diff --git a/README.md b/README.md index 0f10ea049..e6c14f48d 100755 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ A library in Javascript to create graphs in the browser similar to [PureData](ht It can be integrated easily in any existing web applications and graphs can be run without the need of the editor. -![Node Graph](imgs/node_graph_example.PNG "WebGLStudio") +![Node Graph](imgs/node_graph_example.png "WebGLStudio") ## Installation @@ -154,6 +154,7 @@ You can write any feedback to javi.agenjo@gmail.com - kriffe - rappestad +- InventivetalentDev diff --git a/build/litegraph.js b/build/litegraph.js index 092d9270a..2c5218789 100644 --- a/build/litegraph.js +++ b/build/litegraph.js @@ -1,17412 +1,17583 @@ -(function(global){ -// ************************************************************* -// LiteGraph CLASS ******* -// ************************************************************* - -/* FYI: links are stored in graph.links with this structure per object -{ - id: number - type: string, - origin_id: number, - origin_slot: number, - target_id: number, - target_slot: number, - data: * -}; -*/ - -/** -* The Global Scope. It contains all the registered node classes. -* -* @class LiteGraph -* @constructor -*/ - -var LiteGraph = global.LiteGraph = { - - NODE_TITLE_HEIGHT: 20, - NODE_SLOT_HEIGHT: 15, - NODE_WIDGET_HEIGHT: 20, - NODE_WIDTH: 140, - NODE_MIN_WIDTH: 50, - NODE_COLLAPSED_RADIUS: 10, - NODE_COLLAPSED_WIDTH: 80, - CANVAS_GRID_SIZE: 10, - NODE_TITLE_COLOR: "#999", - NODE_TEXT_SIZE: 14, - NODE_TEXT_COLOR: "#AAA", - NODE_SUBTEXT_SIZE: 12, - NODE_DEFAULT_COLOR: "#333", - NODE_DEFAULT_BGCOLOR: "#444", - NODE_DEFAULT_BOXCOLOR: "#888", - NODE_DEFAULT_SHAPE: "box", - MAX_NUMBER_OF_NODES: 1000, //avoid infinite loops - DEFAULT_POSITION: [100,100],//default node position - node_images_path: "", - - VALID_SHAPES: ["default","box","round","card"], //,"circle" - - BOX_SHAPE: 1, - ROUND_SHAPE: 2, - CIRCLE_SHAPE: 3, - CARD_SHAPE: 4, - ARROW_SHAPE: 5, - - //enums - INPUT: 1, - OUTPUT: 2, - - EVENT: -1, //for outputs - ACTION: -1, //for inputs - - ALWAYS: 0, - ON_EVENT: 1, - NEVER: 2, - ON_TRIGGER: 3, - - NORMAL_TITLE: 0, - NO_TITLE: 1, - TRANSPARENT_TITLE: 2, - AUTOHIDE_TITLE: 3, - - proxy: null, //used to redirect calls - - debug: false, - throw_errors: true, - allow_scripts: true, - registered_node_types: {}, //nodetypes by string - node_types_by_file_extension: {}, //used for droping files in the canvas - Nodes: {}, //node types by classname - - /** - * Register a node class so it can be listed when the user wants to create a new one - * @method registerNodeType - * @param {String} type name of the node and path - * @param {Class} base_class class containing the structure of a node - */ - - registerNodeType: function(type, base_class) - { - if(!base_class.prototype) - throw("Cannot register a simple object, it must be a class with a prototype"); - base_class.type = type; - - if(LiteGraph.debug) - console.log("Node registered: " + type); - - var categories = type.split("/"); - var classname = base_class.name; - - var pos = type.lastIndexOf("/"); - base_class.category = type.substr(0,pos); - - if(!base_class.title) - base_class.title = classname; - //info.name = name.substr(pos+1,name.length - pos); - - //extend class - if(base_class.prototype) //is a class - for(var i in LGraphNode.prototype) - if(!base_class.prototype[i]) - base_class.prototype[i] = LGraphNode.prototype[i]; - - Object.defineProperty( base_class.prototype, "shape",{ - set: function(v) { - switch(v) - { - case "default": delete this._shape; break; - case "box": this._shape = LiteGraph.BOX_SHAPE; break; - case "round": this._shape = LiteGraph.ROUND_SHAPE; break; - case "circle": this._shape = LiteGraph.CIRCLE_SHAPE; break; - case "card": this._shape = LiteGraph.CARD_SHAPE; break; - default: - this._shape = v; - } - }, - get: function(v) - { - return this._shape; - }, - enumerable: true - }); - - this.registered_node_types[ type ] = base_class; - if(base_class.constructor.name) - this.Nodes[ classname ] = base_class; - - //warnings - if(base_class.prototype.onPropertyChange) - console.warn("LiteGraph node class " + type + " has onPropertyChange method, it must be called onPropertyChanged with d at the end"); - - if( base_class.supported_extensions ) - { - for(var i in base_class.supported_extensions ) - this.node_types_by_file_extension[ base_class.supported_extensions[i].toLowerCase() ] = base_class; - } - }, - - /** - * Create a new node type by passing a function, it wraps it with a propper class and generates inputs according to the parameters of the function. - * Useful to wrap simple methods that do not require properties, and that only process some input to generate an output. - * @method wrapFunctionAsNode - * @param {String} name node name with namespace (p.e.: 'math/sum') - * @param {Function} func - * @param {Array} param_types [optional] an array containing the type of every parameter, otherwise parameters will accept any type - * @param {String} return_type [optional] string with the return type, otherwise it will be generic - */ - wrapFunctionAsNode: function( name, func, param_types, return_type ) - { - var params = Array(func.length); - var code = ""; - var names = LiteGraph.getParameterNames( func ); - for(var i = 0; i < names.length; ++i) - code += "this.addInput('"+names[i]+"',"+(param_types && param_types[i] ? "'" + param_types[i] + "'" : "0") + ");\n"; - code += "this.addOutput('out',"+( return_type ? "'" + return_type + "'" : 0 )+");\n"; - var classobj = Function(code); - classobj.title = name.split("/").pop(); - classobj.desc = "Generated from " + func.name; - classobj.prototype.onExecute = function onExecute() - { - for(var i = 0; i < params.length; ++i) - params[i] = this.getInputData(i); - var r = func.apply( this, params ); - this.setOutputData(0,r); - } - this.registerNodeType( name, classobj ); - }, - - /** - * Adds this method to all nodetypes, existing and to be created - * (You can add it to LGraphNode.prototype but then existing node types wont have it) - * @method addNodeMethod - * @param {Function} func - */ - addNodeMethod: function( name, func ) - { - LGraphNode.prototype[name] = func; - for(var i in this.registered_node_types) - { - var type = this.registered_node_types[i]; - if(type.prototype[name]) - type.prototype["_" + name] = type.prototype[name]; //keep old in case of replacing - type.prototype[name] = func; - } - }, - - /** - * Create a node of a given type with a name. The node is not attached to any graph yet. - * @method createNode - * @param {String} type full name of the node class. p.e. "math/sin" - * @param {String} name a name to distinguish from other nodes - * @param {Object} options to set options - */ - - createNode: function( type, title, options ) - { - var base_class = this.registered_node_types[type]; - if (!base_class) - { - if(LiteGraph.debug) - console.log("GraphNode type \"" + type + "\" not registered."); - return null; - } - - var prototype = base_class.prototype || base_class; - - title = title || base_class.title || type; - - var node = new base_class( title ); - node.type = type; - - if(!node.title && title) node.title = title; - if(!node.properties) node.properties = {}; - if(!node.properties_info) node.properties_info = []; - if(!node.flags) node.flags = {}; - if(!node.size) node.size = node.computeSize(); - if(!node.pos) node.pos = LiteGraph.DEFAULT_POSITION.concat(); - if(!node.mode) node.mode = LiteGraph.ALWAYS; - - //extra options - if(options) - { - for(var i in options) - node[i] = options[i]; - } - - return node; - }, - - /** - * Returns a registered node type with a given name - * @method getNodeType - * @param {String} type full name of the node class. p.e. "math/sin" - * @return {Class} the node class - */ - - getNodeType: function(type) - { - return this.registered_node_types[type]; - }, - - - /** - * Returns a list of node types matching one category - * @method getNodeType - * @param {String} category category name - * @return {Array} array with all the node classes - */ - - getNodeTypesInCategory: function( category, filter ) - { - var r = []; - for(var i in this.registered_node_types) - { - var type = this.registered_node_types[i]; - if(filter && type.filter && type.filter != filter) - continue; - - if(category == "" ) - { - if (type.category == null) - r.push(type); - } - else if (type.category == category) - r.push(type); - } - - return r; - }, - - /** - * Returns a list with all the node type categories - * @method getNodeTypesCategories - * @return {Array} array with all the names of the categories - */ - - getNodeTypesCategories: function() - { - var categories = {"":1}; - for(var i in this.registered_node_types) - if(this.registered_node_types[i].category && !this.registered_node_types[i].skip_list) - categories[ this.registered_node_types[i].category ] = 1; - var result = []; - for(var i in categories) - result.push(i); - return result; - }, - - //debug purposes: reloads all the js scripts that matches a wilcard - reloadNodes: function (folder_wildcard) - { - var tmp = document.getElementsByTagName("script"); - //weird, this array changes by its own, so we use a copy - var script_files = []; - for(var i in tmp) - script_files.push(tmp[i]); - - - var docHeadObj = document.getElementsByTagName("head")[0]; - folder_wildcard = document.location.href + folder_wildcard; - - for(var i in script_files) - { - var src = script_files[i].src; - if( !src || src.substr(0,folder_wildcard.length ) != folder_wildcard) - continue; - - try - { - if(LiteGraph.debug) - console.log("Reloading: " + src); - var dynamicScript = document.createElement("script"); - dynamicScript.type = "text/javascript"; - dynamicScript.src = src; - docHeadObj.appendChild(dynamicScript); - docHeadObj.removeChild(script_files[i]); - } - catch (err) - { - if(LiteGraph.throw_errors) - throw err; - if(LiteGraph.debug) - console.log("Error while reloading " + src); - } - } - - if(LiteGraph.debug) - console.log("Nodes reloaded"); - }, - - //separated just to improve if it doesnt work - cloneObject: function(obj, target) - { - if(obj == null) return null; - var r = JSON.parse( JSON.stringify( obj ) ); - if(!target) return r; - - for(var i in r) - target[i] = r[i]; - return target; - }, - - isValidConnection: function( type_a, type_b ) - { - if( !type_a || //generic output - !type_b || //generic input - type_a == type_b || //same type (is valid for triggers) - type_a == LiteGraph.EVENT && type_b == LiteGraph.ACTION ) - return true; - - // Enforce string type to handle toLowerCase call (-1 number not ok) - type_a = String(type_a); - type_b = String(type_b); - type_a = type_a.toLowerCase(); - type_b = type_b.toLowerCase(); - - // For nodes supporting multiple connection types - if( type_a.indexOf(",") == -1 && type_b.indexOf(",") == -1 ) - return type_a == type_b; - - // Check all permutations to see if one is valid - var supported_types_a = type_a.split(","); - var supported_types_b = type_b.split(","); - for(var i = 0; i < supported_types_a.length; ++i) - for(var j = 0; j < supported_types_b.length; ++j) - if( supported_types_a[i] == supported_types_b[j] ) - return true; - - return false; - } -}; - -//timer that works everywhere -if(typeof(performance) != "undefined") - LiteGraph.getTime = performance.now.bind(performance); -else if(typeof(Date) != "undefined" && Date.now) - LiteGraph.getTime = Date.now.bind(Date); -else if(typeof(process) != "undefined") - LiteGraph.getTime = function(){ - var t = process.hrtime(); - return t[0]*0.001 + t[1]*(1e-6); - } -else - LiteGraph.getTime = function getTime() { return (new Date).getTime(); } - - - - - - -//********************************************************************************* -// LGraph CLASS -//********************************************************************************* - -/** -* 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. -* -* @class LGraph -* @constructor -* @param {Object} o data from previous serialization [optional] -*/ - -function LGraph( o ) -{ - if (LiteGraph.debug) - console.log("Graph created"); - this.list_of_graphcanvas = null; - this.clear(); - - if(o) - this.configure(o); -} - -global.LGraph = LiteGraph.LGraph = LGraph; - -//default supported types -LGraph.supported_types = ["number","string","boolean"]; - -//used to know which types of connections support this graph (some graphs do not allow certain types) -LGraph.prototype.getSupportedTypes = function() { return this.supported_types || LGraph.supported_types; } - -LGraph.STATUS_STOPPED = 1; -LGraph.STATUS_RUNNING = 2; - -/** -* Removes all nodes from this graph -* @method clear -*/ - -LGraph.prototype.clear = function() -{ - this.stop(); - this.status = LGraph.STATUS_STOPPED; - - this.last_node_id = 1; - this.last_link_id = 1; - - this._version = -1; //used to detect changes - - //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 - - //other scene stuff - this._groups = []; - - //links - this.links = {}; //container with all the links - - //iterations - this.iteration = 0; - - //custom data - this.config = {}; - - //timing - this.globaltime = 0; - this.runningtime = 0; - this.fixedtime = 0; - this.fixedtime_lapse = 0.01; - this.elapsed_time = 0.01; - this.last_update_time = 0; - this.starttime = 0; - - this.catch_errors = true; - - //subgraph_data - this.global_inputs = {}; - this.global_outputs = {}; - - //notify canvas to redraw - this.change(); - - this.sendActionToCanvas("clear"); -} - -/** -* Attach Canvas to this graph -* @method attachCanvas -* @param {GraphCanvas} graph_canvas -*/ - -LGraph.prototype.attachCanvas = function(graphcanvas) -{ - if(graphcanvas.constructor != LGraphCanvas) - throw("attachCanvas expects a LGraphCanvas instance"); - if(graphcanvas.graph && graphcanvas.graph != this) - graphcanvas.graph.detachCanvas( graphcanvas ); - - graphcanvas.graph = this; - if(!this.list_of_graphcanvas) - this.list_of_graphcanvas = []; - this.list_of_graphcanvas.push(graphcanvas); -} - -/** -* Detach Canvas from this graph -* @method detachCanvas -* @param {GraphCanvas} graph_canvas -*/ -LGraph.prototype.detachCanvas = function(graphcanvas) -{ - if(!this.list_of_graphcanvas) - return; - - var pos = this.list_of_graphcanvas.indexOf( graphcanvas ); - if(pos == -1) - return; - graphcanvas.graph = null; - this.list_of_graphcanvas.splice(pos,1); -} - -/** -* Starts running this graph every interval milliseconds. -* @method start -* @param {number} interval amount of milliseconds between executions, default is 1 -*/ - -LGraph.prototype.start = function(interval) -{ - if( this.status == LGraph.STATUS_RUNNING ) - return; - this.status = LGraph.STATUS_RUNNING; - - if(this.onPlayEvent) - this.onPlayEvent(); - - this.sendEventToAllNodes("onStart"); - - //launch - this.starttime = LiteGraph.getTime(); - this.last_update_time = this.starttime; - interval = interval || 1; - var that = this; - - this.execution_timer_id = setInterval( function() { - //execute - that.runStep(1, !this.catch_errors ); - },interval); -} - -/** -* Stops the execution loop of the graph -* @method stop execution -*/ - -LGraph.prototype.stop = function() -{ - if( this.status == LGraph.STATUS_STOPPED ) - return; - - this.status = LGraph.STATUS_STOPPED; - - if(this.onStopEvent) - this.onStopEvent(); - - if(this.execution_timer_id != null) - clearInterval(this.execution_timer_id); - this.execution_timer_id = null; - - this.sendEventToAllNodes("onStop"); -} - -/** -* Run N steps (cycles) of the graph -* @method runStep -* @param {number} num number of steps to run, default is 1 -*/ - -LGraph.prototype.runStep = function( num, do_not_catch_errors ) -{ - num = num || 1; - - var start = LiteGraph.getTime(); - this.globaltime = 0.001 * (start - this.starttime); - - var nodes = this._nodes_executable ? this._nodes_executable : this._nodes; - if(!nodes) - return; - - if( do_not_catch_errors ) - { - //iterations - for(var i = 0; i < num; i++) - { - for( var j = 0, l = nodes.length; j < l; ++j ) - { - var node = nodes[j]; - if( node.mode == LiteGraph.ALWAYS && node.onExecute ) - node.onExecute(); - } - - this.fixedtime += this.fixedtime_lapse; - if( this.onExecuteStep ) - this.onExecuteStep(); - } - - if( this.onAfterExecute ) - this.onAfterExecute(); - } - else - { - try - { - //iterations - for(var i = 0; i < num; i++) - { - for( var j = 0, l = nodes.length; j < l; ++j ) - { - var node = nodes[j]; - if( node.mode == LiteGraph.ALWAYS && node.onExecute ) - node.onExecute(); - } - - this.fixedtime += this.fixedtime_lapse; - if( this.onExecuteStep ) - this.onExecuteStep(); - } - - if( this.onAfterExecute ) - this.onAfterExecute(); - this.errors_in_execution = false; - } - catch (err) - { - this.errors_in_execution = true; - if(LiteGraph.throw_errors) - throw err; - if(LiteGraph.debug) - console.log("Error during execution: " + err); - this.stop(); - } - } - - var now = LiteGraph.getTime(); - var elapsed = now - start; - if (elapsed == 0) - elapsed = 1; - this.execution_time = 0.001 * elapsed; - this.globaltime += 0.001 * elapsed; - this.iteration += 1; - this.elapsed_time = (now - this.last_update_time) * 0.001; - this.last_update_time = now; -} - -/** -* Updates the graph execution order according to relevance of the nodes (nodes with only outputs have more relevance than -* nodes with only inputs. -* @method updateExecutionOrder -*/ -LGraph.prototype.updateExecutionOrder = function() -{ - this._nodes_in_order = this.computeExecutionOrder( false ); - this._nodes_executable = []; - for(var i = 0; i < this._nodes_in_order.length; ++i) - if( this._nodes_in_order[i].onExecute ) - this._nodes_executable.push( this._nodes_in_order[i] ); -} - -//This is more internal, it computes the order and returns it -LGraph.prototype.computeExecutionOrder = function( only_onExecute, set_level ) -{ - var L = []; - var S = []; - var M = {}; - var visited_links = {}; //to avoid repeating links - var remaining_links = {}; //to a - - //search for the nodes without inputs (starting nodes) - for (var i = 0, l = this._nodes.length; i < l; ++i) - { - var node = this._nodes[i]; - if( only_onExecute && !node.onExecute ) - continue; - - M[node.id] = node; //add to pending nodes - - var num = 0; //num of input connections - if(node.inputs) - for(var j = 0, l2 = node.inputs.length; j < l2; j++) - if(node.inputs[j] && node.inputs[j].link != null) - num += 1; - - if(num == 0) //is a starting node - { - S.push(node); - if(set_level) - node._level = 1; - } - else //num of input links - { - if(set_level) - node._level = 0; - remaining_links[node.id] = num; - } - } - - while(true) - { - if(S.length == 0) - break; - - //get an starting node - var node = S.shift(); - L.push(node); //add to ordered list - delete M[node.id]; //remove from the pending nodes - - if(!node.outputs) - continue; - - //for every output - for(var i = 0; i < node.outputs.length; i++) - { - var output = node.outputs[i]; - //not connected - if(output == null || output.links == null || output.links.length == 0) - continue; - - //for every connection - for(var j = 0; j < output.links.length; j++) - { - var link_id = output.links[j]; - var link = this.links[link_id]; - if(!link) - continue; - - //already visited link (ignore it) - if(visited_links[ link.id ]) - continue; - - var target_node = this.getNodeById( link.target_id ); - if(target_node == null) - { - visited_links[ link.id ] = true; - continue; - } - - if(set_level && (!target_node._level || target_node._level <= node._level)) - target_node._level = node._level + 1; - - visited_links[link.id] = true; //mark as visited - remaining_links[target_node.id] -= 1; //reduce the number of links remaining - if (remaining_links[ target_node.id ] == 0) - S.push(target_node); //if no more links, then add to starters array - } - } - } - - //the remaining ones (loops) - for(var i in M) - L.push( M[i] ); - - 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; ++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; -} - -/** -* Returns all the nodes that could affect this one (ancestors) by crawling all the inputs recursively. -* It doesnt include the node itself -* @method getAncestors -* @return {Array} an array with all the LGraphNodes that affect this node, in order of execution -*/ -LGraph.prototype.getAncestors = function( node ) -{ - var ancestors = []; - var pending = [node]; - var visited = {}; - - while (pending.length) - { - var current = pending.shift(); - if(!current.inputs) - continue; - if( !visited[ current.id ] && current != node ) - { - visited[ current.id ] = true; - ancestors.push( current ); - } - - for(var i = 0; i < current.inputs.length;++i) - { - var input = current.getInputNode(i); - if( input && ancestors.indexOf( input ) == -1) - { - pending.push( input ); - } - } - } - - ancestors.sort(function(a,b){ return a.order - b.order;}); - return ancestors; -} - -/** -* Positions every node in a more readable manner -* @method arrange -*/ -LGraph.prototype.arrange = function( margin ) -{ - margin = margin || 40; - - var nodes = this.computeExecutionOrder( false, true ); - var columns = []; - for(var i = 0; i < nodes.length; ++i) - { - var node = nodes[i]; - var col = node._level || 1; - if(!columns[col]) - columns[col] = []; - columns[col].push( node ); - } - - var x = margin; - - for(var i = 0; i < columns.length; ++i) - { - var column = columns[i]; - if(!column) - continue; - var max_size = 100; - var y = margin; - for(var j = 0; j < column.length; ++j) - { - var node = column[j]; - node.pos[0] = x; - node.pos[1] = y; - if(node.size[0] > max_size) - max_size = node.size[0]; - y += node.size[1] + margin; - } - x += max_size + margin; - } - - this.setDirtyCanvas(true,true); -} - - -/** -* Returns the amount of time the graph has been running in milliseconds -* @method getTime -* @return {number} number of milliseconds the graph has been running -*/ -LGraph.prototype.getTime = function() -{ - return this.globaltime; -} - -/** -* Returns the amount of time accumulated using the fixedtime_lapse var. This is used in context where the time increments should be constant -* @method getFixedTime -* @return {number} number of milliseconds the graph has been running -*/ - -LGraph.prototype.getFixedTime = function() -{ - return this.fixedtime; -} - -/** -* Returns the amount of time it took to compute the latest iteration. Take into account that this number could be not correct -* if the nodes are using graphical actions -* @method getElapsedTime -* @return {number} number of milliseconds it took the last cycle -*/ - -LGraph.prototype.getElapsedTime = function() -{ - return this.elapsed_time; -} - -/** -* Sends an event to all the nodes, useful to trigger stuff -* @method sendEventToAllNodes -* @param {String} eventname the name of the event (function to be called) -* @param {Array} params parameters in array format -*/ - -LGraph.prototype.sendEventToAllNodes = function( eventname, params, mode ) -{ - mode = mode || LiteGraph.ALWAYS; - - var nodes = this._nodes_in_order ? this._nodes_in_order : this._nodes; - if(!nodes) - return; - - for( var j = 0, l = nodes.length; j < l; ++j ) - { - var node = nodes[j]; - if(node[eventname] && node.mode == mode ) - { - if(params === undefined) - node[eventname](); - else if(params && params.constructor === Array) - node[eventname].apply( node, params ); - else - node[eventname](params); - } - } -} - -LGraph.prototype.sendActionToCanvas = function(action, params) -{ - if(!this.list_of_graphcanvas) - return; - - for(var i = 0; i < this.list_of_graphcanvas.length; ++i) - { - var c = this.list_of_graphcanvas[i]; - if( c[action] ) - c[action].apply(c, params); - } -} - -/** -* Adds a new node instasnce to this graph -* @method add -* @param {LGraphNode} node the instance of the node -*/ - -LGraph.prototype.add = function( node, skip_compute_order) -{ - if(!node) - return; - - //groups - if( node.constructor === LGraphGroup ) - { - this._groups.push( node ); - this.setDirtyCanvas(true); - this.change(); - node.graph = this; - this._version++; - return; - } - - //nodes - if(node.id != -1 && this._nodes_by_id[node.id] != null) - { - console.warn("LiteGraph: there is already a node with this ID, changing it"); - node.id = ++this.last_node_id; - } - - if(this._nodes.length >= LiteGraph.MAX_NUMBER_OF_NODES) - throw("LiteGraph: max number of nodes in a graph reached"); - - //give him an id - if(node.id == null || node.id == -1) - node.id = ++this.last_node_id; - else if (this.last_node_id < node.id) - this.last_node_id = node.id; - - - node.graph = this; - this._version++; - - this._nodes.push(node); - this._nodes_by_id[node.id] = node; - - if(node.onAdded) - node.onAdded( this ); - - if(this.config.align_to_grid) - node.alignToGrid(); - - if(!skip_compute_order) - this.updateExecutionOrder(); - - if(this.onNodeAdded) - this.onNodeAdded(node); - - - this.setDirtyCanvas(true); - this.change(); - - return node; //to chain actions -} - -/** -* Removes a node from the graph -* @method remove -* @param {LGraphNode} node the instance of the node -*/ - -LGraph.prototype.remove = function(node) -{ - if(node.constructor === LiteGraph.LGraphGroup) - { - var index = this._groups.indexOf(node); - if(index != -1) - this._groups.splice(index,1); - node.graph = null; - this._version++; - this.setDirtyCanvas(true,true); - this.change(); - return; - } - - if(this._nodes_by_id[node.id] == null) - return; //not found - - if(node.ignore_remove) - return; //cannot be removed - - //disconnect inputs - if(node.inputs) - for(var i = 0; i < node.inputs.length; i++) - { - var slot = node.inputs[i]; - if(slot.link != null) - node.disconnectInput(i); - } - - //disconnect outputs - if(node.outputs) - for(var i = 0; i < node.outputs.length; i++) - { - var slot = node.outputs[i]; - if(slot.links != null && slot.links.length) - node.disconnectOutput(i); - } - - //node.id = -1; //why? - - //callback - if(node.onRemoved) - node.onRemoved(); - - node.graph = null; - this._version++; - - //remove from canvas render - if(this.list_of_graphcanvas) - { - for(var i = 0; i < this.list_of_graphcanvas.length; ++i) - { - var canvas = this.list_of_graphcanvas[i]; - if(canvas.selected_nodes[node.id]) - delete canvas.selected_nodes[node.id]; - if(canvas.node_dragged == node) - canvas.node_dragged = null; - } - } - - //remove from containers - var pos = this._nodes.indexOf(node); - if(pos != -1) - this._nodes.splice(pos,1); - delete this._nodes_by_id[node.id]; - - if(this.onNodeRemoved) - this.onNodeRemoved(node); - - this.setDirtyCanvas(true,true); - this.change(); - - this.updateExecutionOrder(); -} - -/** -* Returns a node by its id. -* @method getNodeById -* @param {Number} id -*/ - -LGraph.prototype.getNodeById = function( id ) -{ - if( id == null ) - return null; - return this._nodes_by_id[ id ]; -} - -/** -* Returns a list of nodes that matches a class -* @method findNodesByClass -* @param {Class} classObject the class itself (not an string) -* @return {Array} a list with all the nodes of this type -*/ - -LGraph.prototype.findNodesByClass = function(classObject) -{ - var r = []; - for(var i = 0, l = this._nodes.length; i < l; ++i) - if(this._nodes[i].constructor === classObject) - r.push(this._nodes[i]); - return r; -} - -/** -* Returns a list of nodes that matches a type -* @method findNodesByType -* @param {String} type the name of the node type -* @return {Array} a list with all the nodes of this type -*/ - -LGraph.prototype.findNodesByType = function(type) -{ - var type = type.toLowerCase(); - var r = []; - for(var i = 0, l = this._nodes.length; i < l; ++i) - if(this._nodes[i].type.toLowerCase() == type ) - r.push(this._nodes[i]); - return r; -} - -/** -* Returns a list of nodes that matches a name -* @method findNodesByTitle -* @param {String} name the name of the node to search -* @return {Array} a list with all the nodes with this name -*/ - -LGraph.prototype.findNodesByTitle = function(title) -{ - var result = []; - for(var i = 0, l = this._nodes.length; i < l; ++i) - if(this._nodes[i].title == title) - result.push(this._nodes[i]); - return result; -} - -/** -* Returns the top-most node in this position of the canvas -* @method getNodeOnPos -* @param {number} x the x coordinate in canvas space -* @param {number} y the y coordinate in canvas space -* @param {Array} nodes_list a list with all the nodes to search from, by default is all the nodes in the graph -* @return {LGraphNode} the node at this position or null -*/ -LGraph.prototype.getNodeOnPos = function(x,y, nodes_list) -{ - nodes_list = nodes_list || this._nodes; - for (var i = nodes_list.length - 1; i >= 0; i--) - { - var n = nodes_list[i]; - if(n.isPointInside( x, y, 2 )) - return n; - } - return null; -} - -/** -* Returns the top-most group in that position -* @method getGroupOnPos -* @param {number} x the x coordinate in canvas space -* @param {number} y the y coordinate in canvas space -* @return {LGraphGroup} the group or null -*/ -LGraph.prototype.getGroupOnPos = function(x,y) -{ - for (var i = this._groups.length - 1; i >= 0; i--) - { - var g = this._groups[i]; - if(g.isPointInside( x, y, 2 )) - return g; - } - return null; -} - -// ********** GLOBALS ***************** - -/** -* 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 }; - this._version++; - - if(this.onGlobalInputAdded) - this.onGlobalInputAdded(name, type); - - if(this.onGlobalsChange) - this.onGlobalsChange(); -} - -/** -* 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]; - if (!input) - return; - input.value = data; -} - -/** -* 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]; - if (!input) - return null; - return input.value; -} - -/** -* 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) - return; - - if(!this.global_inputs[old_name]) - return false; - - if(this.global_inputs[name]) - { - console.error("there is already one input with that name"); - return false; - } - - this.global_inputs[name] = this.global_inputs[old_name]; - delete this.global_inputs[old_name]; - this._version++; - - if(this.onGlobalInputRenamed) - this.onGlobalInputRenamed(old_name, name); - - if(this.onGlobalsChange) - 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]) - return false; - - if(this.global_inputs[name].type && this.global_inputs[name].type.toLowerCase() == type.toLowerCase() ) - return; - - this.global_inputs[name].type = type; - this._version++; - if(this.onGlobalInputTypeChanged) - 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]) - return false; - - delete this.global_inputs[name]; - this._version++; - - if(this.onGlobalInputRemoved) - this.onGlobalInputRemoved(name); - - if(this.onGlobalsChange) - this.onGlobalsChange(); - 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 }; - this._version++; - - if(this.onGlobalOutputAdded) - this.onGlobalOutputAdded(name, type); - - if(this.onGlobalsChange) - this.onGlobalsChange(); -} - -/** -* 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 ]; - if (!output) - return; - output.value = value; -} - -/** -* 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]; - if (!output) - return null; - 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; - - -/** -* 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]) - return false; - - if(this.global_outputs[name]) - { - console.error("there is already one output with that name"); - return false; - } - - this.global_outputs[name] = this.global_outputs[old_name]; - delete this.global_outputs[old_name]; - this._version++; - - if(this.onGlobalOutputRenamed) - this.onGlobalOutputRenamed(old_name, name); - - if(this.onGlobalsChange) - 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]) - return false; - - if(this.global_outputs[name].type && this.global_outputs[name].type.toLowerCase() == type.toLowerCase() ) - return; - - this.global_outputs[name].type = type; - this._version++; - if(this.onGlobalOutputTypeChanged) - this.onGlobalOutputTypeChanged(name, type); -} - -/** -* Removes a global graph output -* @method removeGlobalOutput -* @param {String} name -*/ -LGraph.prototype.removeGlobalOutput = function(name) -{ - if(!this.global_outputs[name]) - return false; - delete this.global_outputs[name]; - this._version++; - - if(this.onGlobalOutputRemoved) - this.onGlobalOutputRemoved(name); - - if(this.onGlobalsChange) - this.onGlobalsChange(); - return true; -} - -LGraph.prototype.triggerInput = function(name,value) -{ - 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.findNodesByTitle(name); - for(var i = 0; i < nodes.length; ++i) - nodes[i].setTrigger(func); -} - - -LGraph.prototype.connectionChange = function( node ) -{ - this.updateExecutionOrder(); - if( this.onConnectionChange ) - this.onConnectionChange( node ); - this._version++; - this.sendActionToCanvas("onConnectionChange"); -} - -/** -* returns if the graph is in live mode -* @method isLive -*/ - -LGraph.prototype.isLive = function() -{ - if(!this.list_of_graphcanvas) - return false; - - for(var i = 0; i < this.list_of_graphcanvas.length; ++i) - { - var c = this.list_of_graphcanvas[i]; - if(c.live_mode) - return true; - } - return false; -} - -/* Called when something visually changed (not the graph!) */ -LGraph.prototype.change = function() -{ - if(LiteGraph.debug) - console.log("Graph changed"); - this.sendActionToCanvas("setDirty",[true,true]); - if(this.on_change) - this.on_change(this); -} - -LGraph.prototype.setDirtyCanvas = function(fg,bg) -{ - this.sendActionToCanvas("setDirty",[fg,bg]); -} - -//save and recover app state *************************************** -/** -* Creates a Object containing all the info about this graph, it can be serialized -* @method serialize -* @return {Object} value of the node -*/ -LGraph.prototype.serialize = function() -{ - var nodes_info = []; - for(var i = 0, l = this._nodes.length; i < l; ++i) - nodes_info.push( this._nodes[i].serialize() ); - - //pack link info into a non-verbose format - var links = []; - for(var i in this.links) //links is an OBJECT - { - var link = this.links[i]; - links.push([ link.id, link.origin_id, link.origin_slot, link.target_id, link.target_slot, link.type ]); - } - - var groups_info = []; - for(var i = 0; i < this._groups.length; ++i) - groups_info.push( this._groups[i].serialize() ); - - var data = { - last_node_id: this.last_node_id, - last_link_id: this.last_link_id, - nodes: nodes_info, - links: links, - groups: groups_info, - config: this.config - }; - - return data; -} - - -/** -* Configure a graph from a JSON string -* @method configure -* @param {String} str configure a graph from a JSON string -* @param {Boolean} returns if there was any error parsing -*/ -LGraph.prototype.configure = function( data, keep_old ) -{ - if(!data) - return; - - if(!keep_old) - this.clear(); - - var nodes = data.nodes; - - //decode links info (they are very verbose) - if(data.links && data.links.constructor === Array) - { - var links = []; - for(var i = 0; i < data.links.length; ++i) - { - var link = data.links[i]; - links[ link[0] ] = { id: link[0], origin_id: link[1], origin_slot: link[2], target_id: link[3], target_slot: link[4], type: link[5] }; - } - data.links = links; - } - - //copy all stored fields - for (var i in data) - this[i] = data[i]; - - var error = false; - - //create nodes - this._nodes = []; - if(nodes) - { - for(var i = 0, l = nodes.length; i < l; ++i) - { - var n_info = nodes[i]; //stored info - var node = LiteGraph.createNode( n_info.type, n_info.title ); - if(!node) - { - if(LiteGraph.debug) - console.log("Node not found: " + n_info.type); - error = true; - continue; - } - - node.id = n_info.id; //id it or it will create a new id - this.add(node, true); //add before configure, otherwise configure cannot create links - } - - //configure nodes afterwards so they can reach each other - for(var i = 0, l = nodes.length; i < l; ++i) - { - var n_info = nodes[i]; - var node = this.getNodeById( n_info.id ); - if(node) - node.configure( n_info ); - } - } - - //groups - this._groups.length = 0; - if( data.groups ) - for(var i = 0; i < data.groups.length; ++i ) - { - var group = new LiteGraph.LGraphGroup(); - group.configure( data.groups[i] ); - this.add( group ); - } - - this.updateExecutionOrder(); - this._version++; - this.setDirtyCanvas(true,true); - return error; -} - -LGraph.prototype.load = function(url) -{ - var that = this; - var req = new XMLHttpRequest(); - req.open('GET', url, true); - req.send(null); - req.onload = function (oEvent) { - if(req.status !== 200) - { - console.error("Error loading graph:",req.status,req.response); - return; - } - var data = JSON.parse( req.response ); - that.configure(data); - } - req.onerror = function(err) - { - console.error("Error loading graph:",err); - } -} - -LGraph.prototype.onNodeTrace = function(node, msg, color) -{ - //TODO -} - -// ************************************************************* -// Node CLASS ******* -// ************************************************************* - -/* - title: string - pos: [x,y] - size: [x,y] - - input|output: every connection - + { name:string, type:string, pos: [x,y]=Optional, direction: "input"|"output", links: Array }); - - flags: - + clip_area: if you render outside the node, it will be cliped - + unsafe_execution: not allowed for safe execution - + skip_repeated_outputs: when adding new outputs, it wont show if there is one already connected - + resizable: if set to false it wont be resizable with the mouse - - supported callbacks: - + onAdded: when added to graph - + onRemoved: when removed from graph - + onStart: when the graph starts playing - + onStop: when the graph stops playing - + onDrawForeground: render the inside widgets inside the node - + onDrawBackground: render the background area inside the node (only in edit mode) - + onMouseDown - + onMouseMove - + onMouseUp - + onMouseEnter - + onMouseLeave - + onExecute: execute the node - + onPropertyChanged: when a property is changed in the panel (return true to skip default behaviour) - + onGetInputs: returns an array of possible inputs - + onGetOutputs: returns an array of possible outputs - + onDblClick - + onSerialize - + onSelected - + onDeselected - + onDropItem : DOM item dropped over the node - + onDropFile : file dropped over the node - + onConnectInput : if returns false the incoming connection will be canceled - + onConnectionsChange : a connection changed (new one or removed) (LiteGraph.INPUT or LiteGraph.OUTPUT, slot, true if connected, link_info, input_info ) -*/ - -/** -* Base Class for all the node type classes -* @class LGraphNode -* @param {String} name a name for the node -*/ - -function LGraphNode(title) -{ - this._ctor(title); -} - -global.LGraphNode = LiteGraph.LGraphNode = LGraphNode; - -LGraphNode.prototype._ctor = function( title ) -{ - this.title = title || "Unnamed"; - this.size = [LiteGraph.NODE_WIDTH,60]; - this.graph = null; - - this._pos = new Float32Array(10,10); - - Object.defineProperty( this, "pos", { - set: function(v) - { - if(!v || v.length < 2) - return; - this._pos[0] = v[0]; - this._pos[1] = v[1]; - }, - get: function() - { - return this._pos; - }, - enumerable: true - }); - - this.id = -1; //not know till not added - this.type = null; - - //inputs available: array of inputs - this.inputs = []; - this.outputs = []; - this.connections = []; - - //local data - this.properties = {}; //for the values - this.properties_info = []; //for the info - - this.data = null; //persistent local data - this.flags = {}; -} - -/** -* configure a node from an object containing the serialized info -* @method configure -*/ -LGraphNode.prototype.configure = function(info) -{ - if(this.graph) - this.graph._version++; - - for (var j in info) - { - if(j == "console") - continue; - - if(j == "properties") - { - //i dont want to clone properties, I want to reuse the old container - for(var k in info.properties) - { - this.properties[k] = info.properties[k]; - if(this.onPropertyChanged) - this.onPropertyChanged(k,info.properties[k]); - } - continue; - } - - if(info[j] == null) - continue; - - else if (typeof(info[j]) == 'object') //object - { - if(this[j] && this[j].configure) - this[j].configure( info[j] ); - else - this[j] = LiteGraph.cloneObject(info[j], this[j]); - } - else //value - this[j] = info[j]; - } - - if(!info.title) - this.title = this.constructor.title; - - if(this.onConnectionsChange) - { - if(this.inputs) - for(var i = 0; i < this.inputs.length; ++i) - { - var input = this.inputs[i]; - var link_info = this.graph ? this.graph.links[ input.link ] : null; - this.onConnectionsChange( LiteGraph.INPUT, i, true, link_info, input ); //link_info has been created now, so its updated - } - - if(this.outputs) - for(var i = 0; i < this.outputs.length; ++i) - { - var output = this.outputs[i]; - if(!output.links) - continue; - for(var j = 0; j < output.links.length; ++j) - { - var link_info = this.graph ? this.graph.links[ output.links[j] ] : null; - this.onConnectionsChange( LiteGraph.OUTPUT, i, true, link_info, output ); //link_info has been created now, so its updated - } - } - } - - //FOR LEGACY, PLEASE REMOVE ON NEXT VERSION - for(var i in this.inputs) - { - var input = this.inputs[i]; - if(!input.link || !input.link.length ) - continue; - var link = input.link; - if(typeof(link) != "object") - continue; - input.link = link[0]; - if(this.graph) - this.graph.links[ link[0] ] = { - id: link[0], - origin_id: link[1], - origin_slot: link[2], - target_id: link[3], - target_slot: link[4] - }; - } - for(var i in this.outputs) - { - var output = this.outputs[i]; - if(!output.links || output.links.length == 0) - continue; - for(var j in output.links) - { - var link = output.links[j]; - if(typeof(link) != "object") - continue; - output.links[j] = link[0]; - } - } - - if( this.onConfigure ) - this.onConfigure( info ); -} - -/** -* serialize the content -* @method serialize -*/ - -LGraphNode.prototype.serialize = function() -{ - //create serialization object - var o = { - id: this.id, - type: this.type, - pos: this.pos, - size: this.size, - data: this.data, - flags: LiteGraph.cloneObject(this.flags), - 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 ); - - if( !o.type ) - o.type = this.constructor.type; - - if( this.color ) - o.color = this.color; - if( this.bgcolor ) - o.bgcolor = this.bgcolor; - if( this.boxcolor ) - o.boxcolor = this.boxcolor; - if( this.shape ) - o.shape = this.shape; - - if(this.onSerialize) - this.onSerialize(o); - - return o; -} - - -/* Creates a clone of this node */ -LGraphNode.prototype.clone = function() -{ - var node = LiteGraph.createNode(this.type); - - //we clone it because serialize returns shared containers - var data = LiteGraph.cloneObject( this.serialize() ); - - //remove links - if(data.inputs) - for(var i = 0; i < data.inputs.length; ++i) - data.inputs[i].link = null; - - if(data.outputs) - for(var i = 0; i < data.outputs.length; ++i) - { - if(data.outputs[i].links) - data.outputs[i].links.length = 0; - } - - delete data["id"]; - //remove links - node.configure(data); - - return node; -} - - -/** -* serialize and stringify -* @method toString -*/ - -LGraphNode.prototype.toString = function() -{ - return JSON.stringify( this.serialize() ); -} -//LGraphNode.prototype.unserialize = function(info) {} //this cannot be done from within, must be done in LiteGraph - - -/** -* get the title string -* @method getTitle -*/ - -LGraphNode.prototype.getTitle = function() -{ - return this.title || this.constructor.title; -} - - - -// Execution ************************* -/** -* sets the output data -* @method setOutputData -* @param {number} slot -* @param {*} data -*/ -LGraphNode.prototype.setOutputData = function(slot, data) -{ - if(!this.outputs) - return; - - //this maybe slow and a niche case - //if(slot && slot.constructor === String) - // slot = this.findOutputSlot(slot); - - if(slot == -1 || slot >= this.outputs.length) - return; - - var output_info = this.outputs[slot]; - if(!output_info) - return; - - //store data in the output itself in case we want to debug - output_info._data = data; - - //if there are connections, pass the data to the connections - if( this.outputs[slot].links ) - { - for(var i = 0; i < this.outputs[slot].links.length; i++) - { - var link_id = this.outputs[slot].links[i]; - this.graph.links[ link_id ].data = data; - } - } -} - -/** -* 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 -* @return {*} data or if it is not connected returns undefined -*/ -LGraphNode.prototype.getInputData = function( slot, force_update ) -{ - if(!this.inputs) - return; //undefined; - - if(slot >= this.inputs.length || this.inputs[slot].link == null) - return; - - var link_id = this.inputs[slot].link; - var link = this.graph.links[ link_id ]; - if(!link) //bug: weird case but it happens sometimes - return null; - - 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; - - if(node.updateOutputData) - node.updateOutputData( link.origin_slot ); - else if(node.onExecute) - node.onExecute(); - - 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 -* @param {number} slot -* @return {boolean} -*/ -LGraphNode.prototype.isInputConnected = function(slot) -{ - if(!this.inputs) - return false; - return (slot < this.inputs.length && this.inputs[slot].link != null); -} - -/** -* tells you info about an input connection (which node, type, etc) -* @method getInputInfo -* @param {number} slot -* @return {Object} object or null { link: id, name: string, type: string or 0 } -*/ -LGraphNode.prototype.getInputInfo = function(slot) -{ - if(!this.inputs) - return null; - if(slot < this.inputs.length) - return this.inputs[slot]; - return null; -} - -/** -* returns the node connected in the input slot -* @method getInputNode -* @param {number} slot -* @return {LGraphNode} node or null -*/ -LGraphNode.prototype.getInputNode = function( slot ) -{ - if(!this.inputs) - return null; - if(slot >= this.inputs.length) - return null; - var input = this.inputs[slot]; - if(!input || input.link === null) - return null; - var link_info = this.graph.links[ input.link ]; - if(!link_info) - return null; - 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 -* @param {number} slot -* @return {Object} object or null -*/ -LGraphNode.prototype.getOutputData = function(slot) -{ - if(!this.outputs) - return null; - if(slot >= this.outputs.length) - return null; - - var info = this.outputs[slot]; - return info._data; -} - - -/** -* tells you info about an output connection (which node, type, etc) -* @method getOutputInfo -* @param {number} slot -* @return {Object} object or null { name: string, type: string, links: [ ids of links in number ] } -*/ -LGraphNode.prototype.getOutputInfo = function(slot) -{ - if(!this.outputs) - return null; - if(slot < this.outputs.length) - return this.outputs[slot]; - return null; -} - - -/** -* tells you if there is a connection in one output slot -* @method isOutputConnected -* @param {number} slot -* @return {boolean} -*/ -LGraphNode.prototype.isOutputConnected = function(slot) -{ - if(!this.outputs) - 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 -* @param {number} slot -* @return {array} -*/ -LGraphNode.prototype.getOutputNodes = function(slot) -{ - if(!this.outputs || this.outputs.length == 0) - return null; - - if(slot >= this.outputs.length) - return null; - - var output = this.outputs[slot]; - if(!output.links || output.links.length == 0) - return null; - - var r = []; - for(var i = 0; i < output.links.length; i++) - { - var link_id = output.links[i]; - var link = this.graph.links[ link_id ]; - if(link) - { - var target_node = this.graph.getNodeById( link.target_id ); - if( target_node ) - r.push( target_node ); - } - } - return r; -} - -/** -* Triggers an event in this node, this will trigger any output with the same name -* @method trigger -* @param {String} event name ( "on_play", ... ) if action is equivalent to false then the event is send to all -* @param {*} param -*/ -LGraphNode.prototype.trigger = function( action, param ) -{ - if( !this.outputs || !this.outputs.length ) - return; - - if(this.graph) - this.graph._last_trigger_time = LiteGraph.getTime(); - - for(var i = 0; i < this.outputs.length; ++i) - { - var output = this.outputs[ i ]; - if(!output || output.type !== LiteGraph.EVENT || (action && output.name != action) ) - continue; - this.triggerSlot( i, param ); - } -} - -/** -* Triggers an slot event in this node -* @method triggerSlot -* @param {Number} slot the index of the output slot -* @param {*} param -*/ -LGraphNode.prototype.triggerSlot = function( slot, param ) -{ - if( !this.outputs ) - return; - - var output = this.outputs[ slot ]; - if( !output ) - return; - - var links = output.links; - if(!links || !links.length) - return; - - if(this.graph) - this.graph._last_trigger_time = LiteGraph.getTime(); - - //for every link attached here - for(var k = 0; k < links.length; ++k) - { - var link_info = this.graph.links[ links[k] ]; - if(!link_info) //not connected - continue; - var node = this.graph.getNodeById( link_info.target_id ); - if(!node) //node not found? - continue; - - //used to mark events in graph - link_info._last_time = LiteGraph.getTime(); - - var target_connection = node.inputs[ link_info.target_slot ]; - - if(node.onAction) - node.onAction( target_connection.name, param ); - else if(node.mode === LiteGraph.ON_TRIGGER) - { - if(node.onExecute) - node.onExecute(param); - } - } -} - -/** -* add a new property to this node -* @method addProperty -* @param {string} name -* @param {*} default_value -* @param {string} type string defining the output type ("vec3","number",...) -* @param {Object} extra_info this can be used to have special properties of the property (like values, etc) -*/ -LGraphNode.prototype.addProperty = function( name, default_value, type, extra_info ) -{ - var o = { name: name, type: type, default_value: default_value }; - if(extra_info) - for(var i in extra_info) - o[i] = extra_info[i]; - if(!this.properties_info) - this.properties_info = []; - this.properties_info.push(o); - if(!this.properties) - this.properties = {}; - this.properties[ name ] = default_value; - return o; -} - - -//connections - -/** -* add a new output slot to use in this node -* @method addOutput -* @param {string} name -* @param {string} type string defining the output type ("vec3","number",...) -* @param {Object} extra_info this can be used to have special properties of an output (label, special color, position, etc) -*/ -LGraphNode.prototype.addOutput = function(name,type,extra_info) -{ - var o = { name: name, type: type, links: null }; - if(extra_info) - for(var i in extra_info) - o[i] = extra_info[i]; - - if(!this.outputs) - this.outputs = []; - this.outputs.push(o); - if(this.onOutputAdded) - this.onOutputAdded(o); - this.size = this.computeSize(); - return o; -} - -/** -* add a new output slot to use in this node -* @method addOutputs -* @param {Array} array of triplets like [[name,type,extra_info],[...]] -*/ -LGraphNode.prototype.addOutputs = function(array) -{ - for(var i = 0; i < array.length; ++i) - { - var info = array[i]; - var o = {name:info[0],type:info[1],link:null}; - if(array[2]) - for(var j in info[2]) - o[j] = info[2][j]; - - if(!this.outputs) - this.outputs = []; - this.outputs.push(o); - if(this.onOutputAdded) - this.onOutputAdded(o); - } - - this.size = this.computeSize(); -} - -/** -* remove an existing output slot -* @method removeOutput -* @param {number} slot -*/ -LGraphNode.prototype.removeOutput = function(slot) -{ - this.disconnectOutput(slot); - this.outputs.splice(slot,1); - this.size = this.computeSize(); - if(this.onOutputRemoved) - this.onOutputRemoved(slot); -} - -/** -* add a new input slot to use in this node -* @method addInput -* @param {string} name -* @param {string} type string defining the input type ("vec3","number",...), it its a generic one use 0 -* @param {Object} extra_info this can be used to have special properties of an input (label, color, position, etc) -*/ -LGraphNode.prototype.addInput = function(name,type,extra_info) -{ - type = type || 0; - var o = {name:name,type:type,link:null}; - if(extra_info) - for(var i in extra_info) - o[i] = extra_info[i]; - - if(!this.inputs) - this.inputs = []; - this.inputs.push(o); - this.size = this.computeSize(); - if(this.onInputAdded) - this.onInputAdded(o); - return o; -} - -/** -* add several new input slots in this node -* @method addInputs -* @param {Array} array of triplets like [[name,type,extra_info],[...]] -*/ -LGraphNode.prototype.addInputs = function(array) -{ - for(var i = 0; i < array.length; ++i) - { - var info = array[i]; - var o = {name:info[0], type:info[1], link:null}; - if(array[2]) - for(var j in info[2]) - o[j] = info[2][j]; - - if(!this.inputs) - this.inputs = []; - this.inputs.push(o); - if(this.onInputAdded) - this.onInputAdded(o); - } - - this.size = this.computeSize(); -} - -/** -* remove an existing input slot -* @method removeInput -* @param {number} slot -*/ -LGraphNode.prototype.removeInput = function(slot) -{ - this.disconnectInput(slot); - this.inputs.splice(slot,1); - this.size = this.computeSize(); - if(this.onInputRemoved) - this.onInputRemoved(slot); -} - -/** -* add an special connection to this node (used for special kinds of graphs) -* @method addConnection -* @param {string} name -* @param {string} type string defining the input type ("vec3","number",...) -* @param {[x,y]} pos position of the connection inside the node -* @param {string} direction if is input or output -*/ -LGraphNode.prototype.addConnection = function(name,type,pos,direction) -{ - var o = { - name: name, - type: type, - pos: pos, - direction: direction, - links: null - }; - this.connections.push( o ); - return o; -} - -/** -* computes the size of a node according to its inputs and output slots -* @method computeSize -* @param {number} minHeight -* @return {number} the total size -*/ -LGraphNode.prototype.computeSize = function( minHeight, out ) -{ - var rows = Math.max( this.inputs ? this.inputs.length : 1, this.outputs ? this.outputs.length : 1); - var size = out || new Float32Array([0,0]); - rows = Math.max(rows, 1); - var font_size = LiteGraph.NODE_TEXT_SIZE; //although it should be graphcanvas.inner_text_font size - size[1] = (this.constructor.slot_start_y || 0) + rows * (font_size + 1) + ( this.widgets ? this.widgets.length : 0 ) * (LiteGraph.NODE_WIDGET_HEIGHT + 4 ) + 4; - - var font_size = font_size; - var title_width = compute_text_size( this.title ); - var input_width = 0; - var output_width = 0; - - if(this.inputs) - for(var i = 0, l = this.inputs.length; i < l; ++i) - { - var input = this.inputs[i]; - var text = input.label || input.name || ""; - var text_width = compute_text_size( text ); - if(input_width < text_width) - input_width = text_width; - } - - if(this.outputs) - for(var i = 0, l = this.outputs.length; i < l; ++i) - { - var output = this.outputs[i]; - var text = output.label || output.name || ""; - var text_width = compute_text_size( text ); - if(output_width < text_width) - output_width = text_width; - } - - size[0] = Math.max( input_width + output_width + 10, title_width ); - size[0] = Math.max( size[0], LiteGraph.NODE_WIDTH ); - - if(this.onResize) - this.onResize(size); - - function compute_text_size( text ) - { - if(!text) - return 0; - return font_size * text.length * 0.6; - } - - return size; -} - -/** -* Allows to pass -* -* @method addWidget -* @return {Float32Array[4]} the total size -*/ -LGraphNode.prototype.addWidget = function( type, name, value, callback, options ) -{ - if(!this.widgets) - this.widgets = []; - var w = { - type: type.toLowerCase(), - name: name, - value: value, - callback: callback, - options: options || {} - }; - - if(w.options.y !== undefined ) - w.y = w.options.y; - - if( type == "combo" && !w.options.values ) - throw("LiteGraph addWidget('combo',...) requires to pass values in options: { values:['red','blue'] }"); - this.widgets.push(w); - return w; -} - - -/** -* returns the bounding of the object, used for rendering purposes -* bounding is: [topleft_cornerx, topleft_cornery, width, height] -* @method getBounding -* @return {Float32Array[4]} the total size -*/ -LGraphNode.prototype.getBounding = function( out ) -{ - out = out || new Float32Array(4); - out[0] = this.pos[0] - 4; - out[1] = this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT; - out[2] = this.size[0] + 4; - out[3] = this.size[1] + LiteGraph.NODE_TITLE_HEIGHT; - return out; -} - -/** -* checks if a point is inside the shape of a node -* @method isPointInside -* @param {number} x -* @param {number} y -* @return {boolean} -*/ -LGraphNode.prototype.isPointInside = function(x,y, margin) -{ - margin = margin || 0; - - var margin_top = this.graph && this.graph.isLive() ? 0 : 20; - if(this.flags && this.flags.collapsed) - { - //if ( distance([x,y], [this.pos[0] + this.size[0]*0.5, this.pos[1] + this.size[1]*0.5]) < LiteGraph.NODE_COLLAPSED_RADIUS) - if( isInsideRectangle( x, y, this.pos[0] - margin, this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT - margin, (this._collapsed_width||LiteGraph.NODE_COLLAPSED_WIDTH) + 2 * margin, LiteGraph.NODE_TITLE_HEIGHT + 2 * margin ) ) - return true; - } - else if ( (this.pos[0] - 4 - margin) < x && (this.pos[0] + this.size[0] + 4 + margin) > x - && (this.pos[1] - margin_top - margin) < y && (this.pos[1] + this.size[1] + margin) > y) - return true; - return false; -} - -/** -* checks if a point is inside a node slot, and returns info about which slot -* @method getSlotInPosition -* @param {number} x -* @param {number} y -* @return {Object} if found the object contains { input|output: slot object, slot: number, link_pos: [x,y] } -*/ -LGraphNode.prototype.getSlotInPosition = function( x, y ) -{ - //search for inputs - if(this.inputs) - for(var i = 0, l = this.inputs.length; i < l; ++i) - { - var input = this.inputs[i]; - var link_pos = this.getConnectionPos( true,i ); - if( isInsideRectangle(x, y, link_pos[0] - 10, link_pos[1] - 5, 20,10) ) - return { input: input, slot: i, link_pos: link_pos, locked: input.locked }; - } - - if(this.outputs) - for(var i = 0, l = this.outputs.length; i < l; ++i) - { - var output = this.outputs[i]; - var link_pos = this.getConnectionPos(false,i); - if( isInsideRectangle(x, y, link_pos[0] - 10, link_pos[1] - 5, 20,10) ) - return { output: output, slot: i, link_pos: link_pos, locked: output.locked }; - } - - return null; -} - -/** -* returns the input slot with a given name (used for dynamic slots), -1 if not found -* @method findInputSlot -* @param {string} name the name of the slot -* @return {number} the slot (-1 if not found) -*/ -LGraphNode.prototype.findInputSlot = function(name) -{ - if(!this.inputs) - return -1; - for(var i = 0, l = this.inputs.length; i < l; ++i) - if(name == this.inputs[i].name) - return i; - return -1; -} - -/** -* returns the output slot with a given name (used for dynamic slots), -1 if not found -* @method findOutputSlot -* @param {string} name the name of the slot -* @return {number} the slot (-1 if not found) -*/ -LGraphNode.prototype.findOutputSlot = function(name) -{ - if(!this.outputs) return -1; - for(var i = 0, l = this.outputs.length; i < l; ++i) - if(name == this.outputs[i].name) - return i; - return -1; -} - -/** -* connect this node output to the input of another node -* @method connect -* @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot) -* @param {LGraphNode} node the target node -* @param {number_or_string} target_slot the input slot of the target node (could be the number of the slot or the string with the name of the slot, or -1 to connect a trigger) -* @return {boolean} if it was connected succesfully -*/ -LGraphNode.prototype.connect = function( slot, target_node, target_slot ) -{ - target_slot = target_slot || 0; - - if(!this.graph) //could be connected before adding it to a graph - { - console.log("Connect: Error, node doesnt belong to any graph. Nodes must be added first to a graph before connecting them."); //due to link ids being associated with graphs - return false; - } - - - //seek for the output slot - if( slot.constructor === String ) - { - slot = this.findOutputSlot(slot); - if(slot == -1) - { - if(LiteGraph.debug) - console.log("Connect: Error, no slot of name " + slot); - return false; - } - } - else if(!this.outputs || slot >= this.outputs.length) - { - if(LiteGraph.debug) - console.log("Connect: Error, slot number not found"); - return false; - } - - if(target_node && target_node.constructor === Number) - target_node = this.graph.getNodeById( target_node ); - if(!target_node) - throw("target node is null"); - - //avoid loopback - if(target_node == this) - return false; - - //you can specify the slot by name - if(target_slot.constructor === String) - { - target_slot = target_node.findInputSlot( target_slot ); - if(target_slot == -1) - { - if(LiteGraph.debug) - console.log("Connect: Error, no slot of name " + target_slot); - return false; - } - } - else if( target_slot === LiteGraph.EVENT ) - { - //search for first slot with event? - /* - //create input for trigger - var input = target_node.addInput("onTrigger", LiteGraph.EVENT ); - target_slot = target_node.inputs.length - 1; //last one is the one created - target_node.mode = LiteGraph.ON_TRIGGER; - */ - return false; - } - else if( !target_node.inputs || target_slot >= target_node.inputs.length ) - { - if(LiteGraph.debug) - console.log("Connect: Error, slot number not found"); - return false; - } - - //if there is something already plugged there, disconnect - if(target_node.inputs[ target_slot ].link != null ) - target_node.disconnectInput( target_slot ); - - //why here?? - this.setDirtyCanvas(false,true); - this.graph.connectionChange( this ); - - var output = this.outputs[slot]; - - //allows nodes to block connection - if(target_node.onConnectInput) - if( target_node.onConnectInput( target_slot, output.type, output ) === false) - return false; - - var input = target_node.inputs[target_slot]; - - if( LiteGraph.isValidConnection( output.type, input.type ) ) - { - var link_info = { - id: this.graph.last_link_id++, - type: input.type, - origin_id: this.id, - origin_slot: slot, - target_id: target_node.id, - target_slot: target_slot - }; - - //add to graph links list - this.graph.links[ link_info.id ] = link_info; - - //connect in output - if( output.links == null ) - output.links = []; - output.links.push( link_info.id ); - //connect in input - target_node.inputs[target_slot].link = link_info.id; - if(this.graph) - this.graph._version++; - if(this.onConnectionsChange) - this.onConnectionsChange( LiteGraph.OUTPUT, slot, true, link_info, output ); //link_info has been created now, so its updated - if(target_node.onConnectionsChange) - target_node.onConnectionsChange( LiteGraph.INPUT, target_slot, true, link_info, input ); - } - - this.setDirtyCanvas(false,true); - this.graph.connectionChange( this ); - - return true; -} - -/** -* disconnect one output to an specific node -* @method disconnectOutput -* @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot) -* @param {LGraphNode} target_node the target node to which this slot is connected [Optional, if not target_node is specified all nodes will be disconnected] -* @return {boolean} if it was disconnected succesfully -*/ -LGraphNode.prototype.disconnectOutput = function( slot, target_node ) -{ - if( slot.constructor === String ) - { - slot = this.findOutputSlot(slot); - if(slot == -1) - { - if(LiteGraph.debug) - console.log("Connect: Error, no slot of name " + slot); - return false; - } - } - else if(!this.outputs || slot >= this.outputs.length) - { - if(LiteGraph.debug) - console.log("Connect: Error, slot number not found"); - return false; - } - - //get output slot - var output = this.outputs[slot]; - if(!output.links || output.links.length == 0) - return false; - - //one of the links - if(target_node) - { - if(target_node.constructor === Number) - target_node = this.graph.getNodeById( target_node ); - if(!target_node) - throw("Target Node not found"); - - for(var i = 0, l = output.links.length; i < l; i++) - { - var link_id = output.links[i]; - var link_info = this.graph.links[ link_id ]; - - //is the link we are searching for... - if( link_info.target_id == target_node.id ) - { - output.links.splice(i,1); //remove here - var input = target_node.inputs[ link_info.target_slot ]; - input.link = null; //remove there - delete this.graph.links[ link_id ]; //remove the link from the links pool - if(this.graph) - this.graph._version++; - if(target_node.onConnectionsChange) - target_node.onConnectionsChange( LiteGraph.INPUT, link_info.target_slot, false, link_info, input ); //link_info hasnt been modified so its ok - if(this.onConnectionsChange) - this.onConnectionsChange( LiteGraph.OUTPUT, slot, false, link_info, output ); - break; - } - } - } - else //all the links - { - for(var i = 0, l = output.links.length; i < l; i++) - { - var link_id = output.links[i]; - var link_info = this.graph.links[ link_id ]; - if(!link_info) //bug: it happens sometimes - continue; - - var target_node = this.graph.getNodeById( link_info.target_id ); - var input = null; - if(this.graph) - this.graph._version++; - if(target_node) - { - input = target_node.inputs[ link_info.target_slot ]; - input.link = null; //remove other side link - if(target_node.onConnectionsChange) - target_node.onConnectionsChange( LiteGraph.INPUT, link_info.target_slot, false, link_info, input ); //link_info hasnt been modified so its ok - } - delete this.graph.links[ link_id ]; //remove the link from the links pool - if(this.onConnectionsChange) - this.onConnectionsChange( LiteGraph.OUTPUT, slot, false, link_info, output ); - } - output.links = null; - } - - - this.setDirtyCanvas(false,true); - this.graph.connectionChange( this ); - return true; -} - -/** -* disconnect one input -* @method disconnectInput -* @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot) -* @return {boolean} if it was disconnected succesfully -*/ -LGraphNode.prototype.disconnectInput = function( slot ) -{ - //seek for the output slot - if( slot.constructor === String ) - { - slot = this.findInputSlot(slot); - if(slot == -1) - { - if(LiteGraph.debug) - console.log("Connect: Error, no slot of name " + slot); - return false; - } - } - else if(!this.inputs || slot >= this.inputs.length) - { - if(LiteGraph.debug) - console.log("Connect: Error, slot number not found"); - return false; - } - - var input = this.inputs[slot]; - if(!input) - return false; - - var link_id = this.inputs[slot].link; - this.inputs[slot].link = null; - - //remove other side - var link_info = this.graph.links[ link_id ]; - if( link_info ) - { - var target_node = this.graph.getNodeById( link_info.origin_id ); - if(!target_node) - return false; - - var output = target_node.outputs[ link_info.origin_slot ]; - if(!output || !output.links || output.links.length == 0) - return false; - - //search in the inputs list for this link - for(var i = 0, l = output.links.length; i < l; i++) - { - if( output.links[i] == link_id ) - { - output.links.splice(i,1); - break; - } - } - - delete this.graph.links[ link_id ]; //remove from the pool - if(this.graph) - this.graph._version++; - if( this.onConnectionsChange ) - this.onConnectionsChange( LiteGraph.INPUT, slot, false, link_info, input ); - if( target_node.onConnectionsChange ) - target_node.onConnectionsChange( LiteGraph.OUTPUT, i, false, link_info, output ); - } - - this.setDirtyCanvas(false,true); - this.graph.connectionChange( this ); - return true; -} - -/** -* returns the center of a connection point in canvas coords -* @method getConnectionPos -* @param {boolean} is_input true if if a input slot, false if it is an output -* @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot) -* @return {[x,y]} the position -**/ -LGraphNode.prototype.getConnectionPos = function( is_input, slot_number ) -{ - if(this.flags.collapsed) - { - if(is_input) - return [this.pos[0], this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT * 0.5]; - else - return [this.pos[0] + (this._collapsed_width || LiteGraph.NODE_COLLAPSED_WIDTH), this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT * 0.5]; - } - - if(is_input && slot_number == -1) - { - return [this.pos[0] + 10, this.pos[1] + 10]; - } - - if(is_input && this.inputs.length > slot_number && this.inputs[slot_number].pos) - return [this.pos[0] + this.inputs[slot_number].pos[0],this.pos[1] + this.inputs[slot_number].pos[1]]; - else if(!is_input && this.outputs.length > slot_number && this.outputs[slot_number].pos) - return [this.pos[0] + this.outputs[slot_number].pos[0],this.pos[1] + this.outputs[slot_number].pos[1]]; - - if(!is_input) //output - return [this.pos[0] + this.size[0] + 1, this.pos[1] + 10 + slot_number * LiteGraph.NODE_SLOT_HEIGHT + (this.constructor.slot_start_y || 0)]; - return [this.pos[0] , this.pos[1] + 10 + slot_number * LiteGraph.NODE_SLOT_HEIGHT + (this.constructor.slot_start_y || 0) ]; -} - -/* Force align to grid */ -LGraphNode.prototype.alignToGrid = function() -{ - this.pos[0] = LiteGraph.CANVAS_GRID_SIZE * Math.round(this.pos[0] / LiteGraph.CANVAS_GRID_SIZE); - this.pos[1] = LiteGraph.CANVAS_GRID_SIZE * Math.round(this.pos[1] / LiteGraph.CANVAS_GRID_SIZE); -} - - -/* Console output */ -LGraphNode.prototype.trace = function(msg) -{ - if(!this.console) - this.console = []; - this.console.push(msg); - if(this.console.length > LGraphNode.MAX_CONSOLE) - this.console.shift(); - - this.graph.onNodeTrace(this,msg); -} - -/* Forces to redraw or the main canvas (LGraphNode) or the bg canvas (links) */ -LGraphNode.prototype.setDirtyCanvas = function(dirty_foreground, dirty_background) -{ - if(!this.graph) - return; - this.graph.sendActionToCanvas("setDirty",[dirty_foreground, dirty_background]); -} - -LGraphNode.prototype.loadImage = function(url) -{ - var img = new Image(); - img.src = LiteGraph.node_images_path + url; - img.ready = false; - - var that = this; - img.onload = function() { - this.ready = true; - that.setDirtyCanvas(true); - } - return img; -} - -//safe LGraphNode action execution (not sure if safe) -/* -LGraphNode.prototype.executeAction = function(action) -{ - if(action == "") return false; - - if( action.indexOf(";") != -1 || action.indexOf("}") != -1) - { - this.trace("Error: Action contains unsafe characters"); - return false; - } - - var tokens = action.split("("); - var func_name = tokens[0]; - if( typeof(this[func_name]) != "function") - { - this.trace("Error: Action not found on node: " + func_name); - return false; - } - - var code = action; - - try - { - var _foo = eval; - eval = null; - (new Function("with(this) { " + code + "}")).call(this); - eval = _foo; - } - catch (err) - { - this.trace("Error executing action {" + action + "} :" + err); - return false; - } - - return true; -} -*/ - -/* Allows to get onMouseMove and onMouseUp events even if the mouse is out of focus */ -LGraphNode.prototype.captureInput = function(v) -{ - if(!this.graph || !this.graph.list_of_graphcanvas) - return; - - var list = this.graph.list_of_graphcanvas; - - for(var i = 0; i < list.length; ++i) - { - var c = list[i]; - //releasing somebody elses capture?! - if(!v && c.node_capturing_input != this) - continue; - - //change - c.node_capturing_input = v ? this : null; - } -} - -/** -* Collapse the node to make it smaller on the canvas -* @method collapse -**/ -LGraphNode.prototype.collapse = function( force ) -{ - this.graph._version++; - if(this.constructor.collapsable === false && !force) - return; - if(!this.flags.collapsed) - this.flags.collapsed = true; - else - this.flags.collapsed = false; - this.setDirtyCanvas(true,true); -} - -/** -* Forces the node to do not move or realign on Z -* @method pin -**/ - -LGraphNode.prototype.pin = function(v) -{ - this.graph._version++; - if(v === undefined) - this.flags.pinned = !this.flags.pinned; - else - this.flags.pinned = v; -} - -LGraphNode.prototype.localToScreen = function(x,y, graphcanvas) -{ - return [(x + this.pos[0]) * graphcanvas.scale + graphcanvas.offset[0], - (y + this.pos[1]) * graphcanvas.scale + graphcanvas.offset[1]]; -} - - - - -function LGraphGroup( title ) -{ - this._ctor( title ); -} - -global.LGraphGroup = LiteGraph.LGraphGroup = LGraphGroup; - -LGraphGroup.prototype._ctor = function( title ) -{ - this.title = title || "Group"; - this._bounding = new Float32Array([10,10,140,80]); - this._pos = this._bounding.subarray(0,2); - this._size = this._bounding.subarray(2,4); - this._nodes = []; - this.color = LGraphCanvas.node_colors.pale_blue ? LGraphCanvas.node_colors.pale_blue.groupcolor : "#AAA"; - this.graph = null; - - Object.defineProperty( this, "pos", { - set: function(v) - { - if(!v || v.length < 2) - return; - this._pos[0] = v[0]; - this._pos[1] = v[1]; - }, - get: function() - { - return this._pos; - }, - enumerable: true - }); - - Object.defineProperty( this, "size", { - set: function(v) - { - if(!v || v.length < 2) - return; - this._size[0] = Math.max(140,v[0]); - this._size[1] = Math.max(80,v[1]); - }, - get: function() - { - return this._size; - }, - enumerable: true - }); -} - -LGraphGroup.prototype.configure = function(o) -{ - this.title = o.title; - this._bounding.set( o.bounding ); - this.color = o.color; -} - -LGraphGroup.prototype.serialize = function() -{ - var b = this._bounding; - return { - title: this.title, - bounding: [ b[0], b[1], b[2], b[3] ], - color: this.color - }; -} - -LGraphGroup.prototype.move = function(deltax, deltay, ignore_nodes) -{ - this._pos[0] += deltax; - this._pos[1] += deltay; - if(ignore_nodes) - return; - for(var i = 0; i < this._nodes.length; ++i) - { - var node = this._nodes[i]; - node.pos[0] += deltax; - node.pos[1] += deltay; - } -} - -LGraphGroup.prototype.recomputeInsideNodes = function() -{ - this._nodes.length = 0; - var nodes = this.graph._nodes; - var node_bounding = new Float32Array(4); - - for(var i = 0; i < nodes.length; ++i) - { - var node = nodes[i]; - node.getBounding( node_bounding ); - if(!overlapBounding( this._bounding, node_bounding )) - continue; //out of the visible area - this._nodes.push( node ); - } -} - -LGraphGroup.prototype.isPointInside = LGraphNode.prototype.isPointInside; -LGraphGroup.prototype.setDirtyCanvas = LGraphNode.prototype.setDirtyCanvas; - -//********************************************************************************* -// LGraphCanvas: LGraph renderer CLASS -//********************************************************************************* - -/** -* The Global Scope. It contains all the registered node classes. -* Valid callbacks are: onNodeSelected, onNodeDeselected, onShowNodePanel, onNodeDblClicked -* -* @class LGraphCanvas -* @constructor -* @param {HTMLCanvas} canvas the canvas where you want to render (it accepts a selector in string format or the canvas element itself) -* @param {LGraph} graph [optional] -* @param {Object} options [optional] { skip_rendering, autoresize } -*/ -function LGraphCanvas( canvas, graph, options ) -{ - options = options || {}; - - //if(graph === undefined) - // throw ("No graph assigned"); - this.background_image = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQBJREFUeNrs1rEKwjAUhlETUkj3vP9rdmr1Ysammk2w5wdxuLgcMHyptfawuZX4pJSWZTnfnu/lnIe/jNNxHHGNn//HNbbv+4dr6V+11uF527arU7+u63qfa/bnmh8sWLBgwYJlqRf8MEptXPBXJXa37BSl3ixYsGDBMliwFLyCV/DeLIMFCxYsWLBMwSt4Be/NggXLYMGCBUvBK3iNruC9WbBgwYJlsGApeAWv4L1ZBgsWLFiwYJmCV/AK3psFC5bBggULloJX8BpdwXuzYMGCBctgwVLwCl7Be7MMFixYsGDBsu8FH1FaSmExVfAxBa/gvVmwYMGCZbBg/W4vAQYA5tRF9QYlv/QAAAAASUVORK5CYII=' - - if(canvas && canvas.constructor === String ) - canvas = document.querySelector( canvas ); - - this.max_zoom = 10; - this.min_zoom = 0.1; - this.zoom_modify_alpha = true; //otherwise it generates ugly patterns when scaling down too much - - this.title_text_font = "bold "+LiteGraph.NODE_TEXT_SIZE+"px Arial"; - this.inner_text_font = "normal "+LiteGraph.NODE_SUBTEXT_SIZE+"px Arial"; - this.node_title_color = LiteGraph.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 = true; - this.use_gradients = false; //set to true to render titlebar with gradients - this.editor_alpha = 1; //used for transition - this.pause_rendering = false; - this.render_shadows = true; - this.clear_background = true; - - this.render_only_selected = true; - this.live_mode = false; - this.show_info = true; - this.allow_dragcanvas = true; - this.allow_dragnodes = true; - this.allow_interaction = true; //allow to control widgets, buttons, collapse, etc - this.allow_searchbox = true; - this.drag_mode = false; - this.dragging_rectangle = null; - - this.filter = null; //allows to filter to only accept some type of nodes in a graph - - this.always_render_background = false; - this.render_canvas_border = true; - this.render_connections_shadows = false; //too much cpu - this.render_connections_border = true; - this.render_curved_connections = true; - this.render_connection_arrows = true; - - this.canvas_mouse = [0,0]; //mouse in canvas graph coordinates, where 0,0 is the top-left corner of the blue rectangle - - //to personalize the search box - this.onSearchBox = null; - this.onSearchBoxSelection = null; - - this.connections_width = 3; - this.round_radius = 8; - - this.current_node = null; - this.node_widget = null; //used for widgets - this.last_mouse_position = [0,0]; - - //link canvas and graph - if(graph) - graph.attachCanvas(this); - - this.setCanvas( canvas ); - this.clear(); - - if(!options.skip_render) - this.startRendering(); - - this.autoresize = options.autoresize; -} - -global.LGraphCanvas = LiteGraph.LGraphCanvas = LGraphCanvas; - -LGraphCanvas.link_type_colors = {"-1":"#F85",'number':"#AAC","node":"#DCA"}; -LGraphCanvas.gradients = {}; //cache of gradients - -/** -* clears all the data inside -* -* @method clear -*/ -LGraphCanvas.prototype.clear = function() -{ - this.frame = 0; - this.last_draw_time = 0; - this.render_time = 0; - this.fps = 0; - - this.scale = 1; - this.offset = [0,0]; - - this.dragging_rectangle = null; - - this.selected_nodes = {}; - this.selected_group = null; - - this.visible_nodes = []; - this.node_dragged = null; - this.node_over = null; - this.node_capturing_input = null; - this.connecting_node = null; - this.highlighted_links = {}; - - this.dirty_canvas = true; - this.dirty_bgcanvas = true; - this.dirty_area = null; - - this.node_in_panel = null; - this.node_widget = null; - - this.last_mouse = [0,0]; - this.last_mouseclick = 0; - - if(this.onClear) - this.onClear(); - //this.UIinit(); -} - -/** -* assigns a graph, you can reasign graphs to the same canvas -* -* @method setGraph -* @param {LGraph} graph -*/ -LGraphCanvas.prototype.setGraph = function( graph, skip_clear ) -{ - if(this.graph == graph) - return; - - if(!skip_clear) - this.clear(); - - if(!graph && this.graph) - { - this.graph.detachCanvas(this); - return; - } - - /* - if(this.graph) - this.graph.canvas = null; //remove old graph link to the canvas - this.graph = graph; - if(this.graph) - this.graph.canvas = this; - */ - graph.attachCanvas(this); - this.setDirty(true,true); -} - -/** -* opens a graph contained inside a node in the current graph -* -* @method openSubgraph -* @param {LGraph} graph -*/ -LGraphCanvas.prototype.openSubgraph = function(graph) -{ - if(!graph) - throw("graph cannot be null"); - - if(this.graph == graph) - throw("graph cannot be the same"); - - this.clear(); - - if(this.graph) - { - if(!this._graph_stack) - this._graph_stack = []; - this._graph_stack.push(this.graph); - } - - graph.attachCanvas(this); - this.setDirty(true,true); -} - -/** -* closes a subgraph contained inside a node -* -* @method closeSubgraph -* @param {LGraph} assigns a graph -*/ -LGraphCanvas.prototype.closeSubgraph = function() -{ - if(!this._graph_stack || this._graph_stack.length == 0) - return; - var graph = this._graph_stack.pop(); - this.selected_nodes = {}; - this.highlighted_links = {}; - graph.attachCanvas(this); - this.setDirty(true,true); -} - -/** -* assigns a canvas -* -* @method setCanvas -* @param {Canvas} assigns a canvas (also accepts the ID of the element (not a selector) -*/ -LGraphCanvas.prototype.setCanvas = function( canvas, skip_events ) -{ - var that = this; - - if(canvas) - { - if( canvas.constructor === String ) - { - canvas = document.getElementById(canvas); - if(!canvas) - throw("Error creating LiteGraph canvas: Canvas not found"); - } - } - - if(canvas === this.canvas) - return; - - if(!canvas && this.canvas) - { - //maybe detach events from old_canvas - if(!skip_events) - this.unbindEvents(); - } - - this.canvas = canvas; - - if(!canvas) - return; - - //this.canvas.tabindex = "1000"; - canvas.className += " lgraphcanvas"; - canvas.data = this; - - //bg canvas: used for non changing stuff - this.bgcanvas = null; - if(!this.bgcanvas) - { - this.bgcanvas = document.createElement("canvas"); - this.bgcanvas.width = this.canvas.width; - this.bgcanvas.height = this.canvas.height; - } - - if(canvas.getContext == null) - { - if( canvas.localName != "canvas" ) - throw("Element supplied for LGraphCanvas must be a element, you passed a " + canvas.localName ); - throw("This browser doesnt support Canvas"); - } - - var ctx = this.ctx = canvas.getContext("2d"); - if(ctx == null) - { - if(!canvas.webgl_enabled) - console.warn("This canvas seems to be WebGL, enabling WebGL renderer"); - this.enableWebGL(); - } - - //input: (move and up could be unbinded) - this._mousemove_callback = this.processMouseMove.bind(this); - this._mouseup_callback = this.processMouseUp.bind(this); - - if(!skip_events) - this.bindEvents(); -} - -//used in some events to capture them -LGraphCanvas.prototype._doNothing = function doNothing(e) { e.preventDefault(); return false; }; -LGraphCanvas.prototype._doReturnTrue = function doNothing(e) { e.preventDefault(); return true; }; - -LGraphCanvas.prototype.bindEvents = function() -{ - if( this._events_binded ) - { - console.warn("LGraphCanvas: events already binded"); - return; - } - - var canvas = this.canvas; - var ref_window = this.getCanvasWindow(); - var document = ref_window.document; //hack used when moving canvas between windows - - this._mousedown_callback = this.processMouseDown.bind(this); - this._mousewheel_callback = this.processMouseWheel.bind(this); - - canvas.addEventListener("mousedown", this._mousedown_callback, true ); //down do not need to store the binded - canvas.addEventListener("mousemove", this._mousemove_callback ); - canvas.addEventListener("mousewheel", this._mousewheel_callback, false); - - canvas.addEventListener("contextmenu", this._doNothing ); - canvas.addEventListener("DOMMouseScroll", this._mousewheel_callback, false); - - //touch events - //if( 'touchstart' in document.documentElement ) - { - canvas.addEventListener("touchstart", this.touchHandler, true); - canvas.addEventListener("touchmove", this.touchHandler, true); - canvas.addEventListener("touchend", this.touchHandler, true); - canvas.addEventListener("touchcancel", this.touchHandler, true); - } - - //Keyboard ****************** - this._key_callback = this.processKey.bind(this); - - canvas.addEventListener("keydown", this._key_callback, true ); - document.addEventListener("keyup", this._key_callback, true ); //in document, otherwise it doesnt fire keyup - - //Droping Stuff over nodes ************************************ - this._ondrop_callback = this.processDrop.bind(this); - - canvas.addEventListener("dragover", this._doNothing, false ); - canvas.addEventListener("dragend", this._doNothing, false ); - canvas.addEventListener("drop", this._ondrop_callback, false ); - canvas.addEventListener("dragenter", this._doReturnTrue, false ); - - this._events_binded = true; -} - -LGraphCanvas.prototype.unbindEvents = function() -{ - if( !this._events_binded ) - { - console.warn("LGraphCanvas: no events binded"); - return; - } - - var ref_window = this.getCanvasWindow(); - var document = ref_window.document; - - this.canvas.removeEventListener( "mousedown", this._mousedown_callback ); - this.canvas.removeEventListener( "mousewheel", this._mousewheel_callback ); - this.canvas.removeEventListener( "DOMMouseScroll", this._mousewheel_callback ); - this.canvas.removeEventListener( "keydown", this._key_callback ); - document.removeEventListener( "keyup", this._key_callback ); - this.canvas.removeEventListener( "contextmenu", this._doNothing ); - this.canvas.removeEventListener( "drop", this._ondrop_callback ); - this.canvas.removeEventListener( "dragenter", this._doReturnTrue ); - - this.canvas.removeEventListener("touchstart", this.touchHandler ); - this.canvas.removeEventListener("touchmove", this.touchHandler ); - this.canvas.removeEventListener("touchend", this.touchHandler ); - this.canvas.removeEventListener("touchcancel", this.touchHandler ); - - this._mousedown_callback = null; - this._mousewheel_callback = null; - this._key_callback = null; - this._ondrop_callback = null; - - this._events_binded = false; -} - -LGraphCanvas.getFileExtension = function (url) -{ - var question = url.indexOf("?"); - if(question != -1) - url = url.substr(0,question); - var point = url.lastIndexOf("."); - if(point == -1) - return ""; - return url.substr(point+1).toLowerCase(); -} - -//this file allows to render the canvas using WebGL instead of Canvas2D -//this is useful if you plant to render 3D objects inside your nodes -LGraphCanvas.prototype.enableWebGL = function() -{ - if(typeof(GL) === undefined) - throw("litegl.js must be included to use a WebGL canvas"); - if(typeof(enableWebGLCanvas) === undefined) - throw("webglCanvas.js must be included to use this feature"); - - this.gl = this.ctx = enableWebGLCanvas(this.canvas); - this.ctx.webgl = true; - this.bgcanvas = this.canvas; - this.bgctx = this.gl; - this.canvas.webgl_enabled = true; - - /* - GL.create({ canvas: this.bgcanvas }); - this.bgctx = enableWebGLCanvas( this.bgcanvas ); - window.gl = this.gl; - */ -} - - -/* -LGraphCanvas.prototype.UIinit = function() -{ - var that = this; - $("#node-console input").change(function(e) - { - if(e.target.value == "") - return; - - var node = that.node_in_panel; - if(!node) - return; - - node.trace("] " + e.target.value, "#333"); - if(node.onConsoleCommand) - { - if(!node.onConsoleCommand(e.target.value)) - node.trace("command not found", "#A33"); - } - else if (e.target.value == "info") - { - node.trace("Special methods:"); - for(var i in node) - { - if(typeof(node[i]) == "function" && LGraphNode.prototype[i] == null && i.substr(0,2) != "on" && i[0] != "_") - node.trace(" + " + i); - } - } - else - { - try - { - eval("var _foo = function() { return ("+e.target.value+"); }"); - var result = _foo.call(node); - if(result) - node.trace(result.toString()); - delete window._foo; - } - catch(err) - { - node.trace("error: " + err, "#A33"); - } - } - - this.value = ""; - }); -} -*/ - -/** -* marks as dirty the canvas, this way it will be rendered again -* -* @class LGraphCanvas -* @method setDirty -* @param {bool} fgcanvas if the foreground canvas is dirty (the one containing the nodes) -* @param {bool} bgcanvas if the background canvas is dirty (the one containing the wires) -*/ -LGraphCanvas.prototype.setDirty = function( fgcanvas, bgcanvas ) -{ - if(fgcanvas) - this.dirty_canvas = true; - if(bgcanvas) - this.dirty_bgcanvas = true; -} - -/** -* Used to attach the canvas in a popup -* -* @method getCanvasWindow -* @return {window} returns the window where the canvas is attached (the DOM root node) -*/ -LGraphCanvas.prototype.getCanvasWindow = function() -{ - if(!this.canvas) - return window; - var doc = this.canvas.ownerDocument; - return doc.defaultView || doc.parentWindow; -} - -/** -* starts rendering the content of the canvas when needed -* -* @method startRendering -*/ -LGraphCanvas.prototype.startRendering = function() -{ - if(this.is_rendering) - return; //already rendering - - this.is_rendering = true; - renderFrame.call(this); - - function renderFrame() - { - if(!this.pause_rendering) - this.draw(); - - var window = this.getCanvasWindow(); - if(this.is_rendering) - window.requestAnimationFrame( renderFrame.bind(this) ); - } -} - -/** -* stops rendering the content of the canvas (to save resources) -* -* @method stopRendering -*/ -LGraphCanvas.prototype.stopRendering = function() -{ - this.is_rendering = false; - /* - if(this.rendering_timer_id) - { - clearInterval(this.rendering_timer_id); - this.rendering_timer_id = null; - } - */ -} - -/* LiteGraphCanvas input */ - -LGraphCanvas.prototype.processMouseDown = function(e) -{ - if(!this.graph) - return; - - this.adjustMouseEvent(e); - - var ref_window = this.getCanvasWindow(); - var document = ref_window.document; - LGraphCanvas.active_canvas = this; - var that = this; - - //move mouse move event to the window in case it drags outside of the canvas - this.canvas.removeEventListener("mousemove", this._mousemove_callback ); - ref_window.document.addEventListener("mousemove", this._mousemove_callback, true ); //catch for the entire window - ref_window.document.addEventListener("mouseup", this._mouseup_callback, true ); - - var node = this.graph.getNodeOnPos( e.canvasX, e.canvasY, this.visible_nodes ); - var skip_dragging = false; - var skip_action = false; - var now = LiteGraph.getTime(); - var is_double_click = (now - this.last_mouseclick) < 300; - - this.canvas_mouse[0] = e.canvasX; - this.canvas_mouse[1] = e.canvasY; - - LiteGraph.closeAllContextMenus( ref_window ); - - if(e.which == 1) //left button mouse - { - if( e.ctrlKey ) - { - this.dragging_rectangle = new Float32Array(4); - this.dragging_rectangle[0] = e.canvasX; - this.dragging_rectangle[1] = e.canvasY; - this.dragging_rectangle[2] = 1; - this.dragging_rectangle[3] = 1; - skip_action = true; - } - - var clicking_canvas_bg = false; - - //when clicked on top of a node - //and it is not interactive - if( node && this.allow_interaction && !skip_action ) - { - if( !this.live_mode && !node.flags.pinned ) - this.bringToFront( node ); //if it wasnt selected? - - //not dragging mouse to connect two slots - if(!this.connecting_node && !node.flags.collapsed && !this.live_mode) - { - //search for outputs - if(node.outputs) - for(var i = 0, l = node.outputs.length; i < l; ++i) - { - var output = node.outputs[i]; - var link_pos = node.getConnectionPos(false,i); - if( isInsideRectangle( e.canvasX, e.canvasY, link_pos[0] - 10, link_pos[1] - 5, 20,10) ) - { - this.connecting_node = node; - this.connecting_output = output; - this.connecting_pos = node.getConnectionPos(false,i); - this.connecting_slot = i; - - if (is_double_click) { - if (node.onOutputDblClick) - node.onOutputDblClick(i, e); - } else { - if (node.onOutputClick) - node.onOutputClick(i, e); - } - - skip_action = true; - break; - } - } - - //search for inputs - if(node.inputs) - for(var i = 0, l = node.inputs.length; i < l; ++i) - { - var input = node.inputs[i]; - var link_pos = node.getConnectionPos( true, i ); - if( isInsideRectangle(e.canvasX, e.canvasY, link_pos[0] - 10, link_pos[1] - 5, 20,10) ) - { - if (is_double_click) { - if (node.onInputDblClick) - node.onInputDblClick(i, e); - } else { - if (node.onInputClick) - node.onInputClick(i, e); - } - - if(input.link !== null) - { - node.disconnectInput(i); - this.dirty_bgcanvas = true; - skip_action = true; - } - } - } - - //Search for corner - if( !skip_action && node.flags.resizable !== false && isInsideRectangle(e.canvasX, e.canvasY, node.pos[0] + node.size[0] - 5, node.pos[1] + node.size[1] - 5 ,5,5 )) - { - this.resizing_node = node; - this.canvas.style.cursor = "se-resize"; - skip_action = true; - } - } - - //Search for corner - if( !skip_action && isInsideRectangle(e.canvasX, e.canvasY, node.pos[0], node.pos[1] - LiteGraph.NODE_TITLE_HEIGHT, LiteGraph.NODE_TITLE_HEIGHT, LiteGraph.NODE_TITLE_HEIGHT )) - { - node.collapse(); - skip_action = true; - } - - //it wasnt clicked on the links boxes - if(!skip_action) - { - var block_drag_node = false; - - //widgets - var widget = this.processNodeWidgets( node, this.canvas_mouse, e ); - if(widget) - { - block_drag_node = true; - this.node_widget = [node, widget]; - } - - //double clicking - if (is_double_click && this.selected_nodes[ node.id ]) - { - //double click node - if( node.onDblClick) - node.onDblClick(e); - this.processNodeDblClicked( node ); - block_drag_node = true; - } - - //if do not capture mouse - if( node.onMouseDown && node.onMouseDown( e, [e.canvasX - node.pos[0], e.canvasY - node.pos[1]] ) ) - block_drag_node = true; - else if(this.live_mode) - { - clicking_canvas_bg = true; - block_drag_node = true; - } - - if(!block_drag_node) - { - if(this.allow_dragnodes) - this.node_dragged = node; - if(!this.selected_nodes[ node.id ]) - this.processNodeSelected( node, e ); - } - - this.dirty_canvas = true; - } - } - else //clicked outside of nodes - { - this.selected_group = this.graph.getGroupOnPos( e.canvasX, e.canvasY ); - this.selected_group_resizing = false; - if( this.selected_group ) - { - if( e.ctrlKey ) - this.dragging_rectangle = null; - - var dist = distance( [e.canvasX, e.canvasY], [ this.selected_group.pos[0] + this.selected_group.size[0], this.selected_group.pos[1] + this.selected_group.size[1] ] ); - if( (dist * this.scale) < 10 ) - this.selected_group_resizing = true; - else - this.selected_group.recomputeInsideNodes(); - } - - if( is_double_click ) - this.showSearchBox( e ); - - clicking_canvas_bg = true; - } - - if( !skip_action && clicking_canvas_bg && this.allow_dragcanvas ) - { - this.dragging_canvas = true; - } - } - else if (e.which == 2) //middle button - { - - } - else if (e.which == 3) //right button - { - this.processContextMenu( node, e ); - } - - //TODO - //if(this.node_selected != prev_selected) - // this.onNodeSelectionChange(this.node_selected); - - this.last_mouse[0] = e.localX; - this.last_mouse[1] = e.localY; - this.last_mouseclick = LiteGraph.getTime(); - - /* - if( (this.dirty_canvas || this.dirty_bgcanvas) && this.rendering_timer_id == null) - this.draw(); - */ - - this.graph.change(); - - //this is to ensure to defocus(blur) if a text input element is on focus - if(!ref_window.document.activeElement || (ref_window.document.activeElement.nodeName.toLowerCase() != "input" && ref_window.document.activeElement.nodeName.toLowerCase() != "textarea")) - e.preventDefault(); - e.stopPropagation(); - - if(this.onMouseDown) - this.onMouseDown(e); - - return false; -} - -LGraphCanvas.prototype.processMouseMove = function(e) -{ - if(this.autoresize) - this.resize(); - - if(!this.graph) - return; - - LGraphCanvas.active_canvas = this; - this.adjustMouseEvent(e); - var mouse = [e.localX, e.localY]; - 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; - - if( this.node_widget ) - { - this.processNodeWidgets( this.node_widget[0], this.canvas_mouse, e, this.node_widget[1] ); - this.dirty_canvas = true; - } - - 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) - { - if( this.selected_group_resizing ) - this.selected_group.size = [ e.canvasX - this.selected_group.pos[0], e.canvasY - this.selected_group.pos[1] ]; - else - { - var deltax = delta[0] / this.scale; - var deltay = delta[1] / this.scale; - this.selected_group.move( deltax, deltay, e.ctrlKey ); - if( this.selected_group._nodes.length) - this.dirty_canvas = true; - } - this.dirty_bgcanvas = true; - } - else if(this.dragging_canvas) - { - this.offset[0] += delta[0] / this.scale; - this.offset[1] += delta[1] / this.scale; - this.dirty_canvas = true; - this.dirty_bgcanvas = true; - } - else if(this.allow_interaction) - { - if(this.connecting_node) - this.dirty_canvas = true; - - //get node over - 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]) - { - //mouse leave - this.graph._nodes[i].mouseOver = false; - if(this.node_over && this.node_over.onMouseLeave) - this.node_over.onMouseLeave(e); - this.node_over = null; - this.dirty_canvas = true; - } - } - - //mouse over a node - if(node) - { - //this.canvas.style.cursor = "move"; - if(!node.mouseOver) - { - //mouse enter - node.mouseOver = true; - this.node_over = node; - this.dirty_canvas = true; - - if(node.onMouseEnter) node.onMouseEnter(e); - } - - //in case the node wants to do something - if(node.onMouseMove) - node.onMouseMove(e); - - //if dragging a link - if(this.connecting_node) - { - var pos = this._highlight_input || [0,0]; //to store the output of isOverNodeInput - - //on top of input - if( this.isOverNodeBox( node, e.canvasX, e.canvasY ) ) - { - //mouse on top of the corner box, dont know what to do - } - else - { - //check if I have a slot below de mouse - 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 ) ) - this._highlight_input = pos; - } - else - this._highlight_input = null; - } - } - - //Search for corner - if(this.canvas) - { - if( isInsideRectangle(e.canvasX, e.canvasY, node.pos[0] + node.size[0] - 5, node.pos[1] + node.size[1] - 5 ,5,5 )) - this.canvas.style.cursor = "se-resize"; - else - this.canvas.style.cursor = null; - } - } - else if(this.canvas) - this.canvas.style.cursor = null; - - if(this.node_capturing_input && this.node_capturing_input != node && this.node_capturing_input.onMouseMove) - { - this.node_capturing_input.onMouseMove(e); - } - - - if(this.node_dragged && !this.live_mode) - { - /* - this.node_dragged.pos[0] += delta[0] / this.scale; - this.node_dragged.pos[1] += delta[1] / this.scale; - this.node_dragged.pos[0] = Math.round(this.node_dragged.pos[0]); - this.node_dragged.pos[1] = Math.round(this.node_dragged.pos[1]); - */ - - for(var i in this.selected_nodes) - { - var n = this.selected_nodes[i]; - - n.pos[0] += delta[0] / this.scale; - n.pos[1] += delta[1] / this.scale; - //n.pos[0] = Math.round(n.pos[0]); - //n.pos[1] = Math.round(n.pos[1]); - } - - this.dirty_canvas = true; - this.dirty_bgcanvas = true; - } - - if(this.resizing_node && !this.live_mode) - { - //convert mouse to node space - this.resizing_node.size[0] = e.canvasX - this.resizing_node.pos[0]; - this.resizing_node.size[1] = e.canvasY - this.resizing_node.pos[1]; - - //constraint size - var max_slots = Math.max( this.resizing_node.inputs ? this.resizing_node.inputs.length : 0, this.resizing_node.outputs ? this.resizing_node.outputs.length : 0); - var min_height = max_slots * LiteGraph.NODE_SLOT_HEIGHT + ( this.resizing_node.widgets ? this.resizing_node.widgets.length : 0 ) * (LiteGraph.NODE_WIDGET_HEIGHT + 4 ) + 4; - if(this.resizing_node.size[1] < min_height ) - this.resizing_node.size[1] = min_height; - if(this.resizing_node.size[0] < LiteGraph.NODE_MIN_WIDTH) - this.resizing_node.size[0] = LiteGraph.NODE_MIN_WIDTH; - - this.canvas.style.cursor = "se-resize"; - this.dirty_canvas = true; - this.dirty_bgcanvas = true; - } - } - - /* - if((this.dirty_canvas || this.dirty_bgcanvas) && this.rendering_timer_id == null) - this.draw(); - */ - - e.preventDefault(); - //e.stopPropagation(); - return false; - //this is not really optimal - //this.graph.change(); -} - -LGraphCanvas.prototype.processMouseUp = function(e) -{ - if(!this.graph) - return; - - var window = this.getCanvasWindow(); - var document = window.document; - LGraphCanvas.active_canvas = this; - - //restore the mousemove event back to the canvas - document.removeEventListener("mousemove", this._mousemove_callback, true ); - this.canvas.addEventListener("mousemove", this._mousemove_callback, true); - document.removeEventListener("mouseup", this._mouseup_callback, true ); - - this.adjustMouseEvent(e); - var now = LiteGraph.getTime(); - e.click_time = (now - this.last_mouseclick); - - if (e.which == 1) //left button - { - this.node_widget = null; - this.selected_group = null; - this.selected_group_resizing = false; - - if( this.dragging_rectangle ) - { - if(this.graph) - { - var nodes = this.graph._nodes; - var node_bounding = new Float32Array(4); - this.deselectAllNodes(); - if( this.dragging_rectangle[2] < 0 ) //flip if negative width - this.dragging_rectangle[0] += this.dragging_rectangle[2]; - if( this.dragging_rectangle[3] < 0 ) //flip if negative height - this.dragging_rectangle[1] += this.dragging_rectangle[3]; - this.dragging_rectangle[2] = Math.abs( this.dragging_rectangle[2] * this.scale ); //abs to convert negative width - this.dragging_rectangle[3] = Math.abs( this.dragging_rectangle[3] * this.scale ); //abs to convert negative height - - for(var i = 0; i < nodes.length; ++i) - { - var node = nodes[i]; - node.getBounding( node_bounding ); - if(!overlapBounding( this.dragging_rectangle, node_bounding )) - continue; //out of the visible area - this.selectNode( node, true ); - } - } - this.dragging_rectangle = null; - } - else if(this.connecting_node) //dragging a connection - { - this.dirty_canvas = true; - this.dirty_bgcanvas = true; - - var node = this.graph.getNodeOnPos( e.canvasX, e.canvasY, this.visible_nodes ); - - //node below mouse - if(node) - { - if( this.connecting_output.type == LiteGraph.EVENT && this.isOverNodeBox( node, e.canvasX, e.canvasY ) ) - { - this.connecting_node.connect( this.connecting_slot, node, LiteGraph.EVENT ); - } - else - { - //slot below mouse? connect - var slot = this.isOverNodeInput(node, e.canvasX, e.canvasY); - if(slot != -1) - { - this.connecting_node.connect(this.connecting_slot, node, slot); - } - else - { //not on top of an input - var input = node.getInputInfo(0); - //auto connect - if(this.connecting_output.type == LiteGraph.EVENT) - this.connecting_node.connect( this.connecting_slot, node, LiteGraph.EVENT ); - else - if(input && !input.link && LiteGraph.isValidConnection( input.type && this.connecting_output.type ) ) - this.connecting_node.connect( this.connecting_slot, node, 0 ); - } - } - } - - this.connecting_output = null; - this.connecting_pos = null; - this.connecting_node = null; - this.connecting_slot = -1; - - }//not dragging connection - else if(this.resizing_node) - { - this.dirty_canvas = true; - this.dirty_bgcanvas = true; - this.resizing_node = null; - } - else if(this.node_dragged) //node being dragged? - { - this.dirty_canvas = true; - this.dirty_bgcanvas = true; - this.node_dragged.pos[0] = Math.round(this.node_dragged.pos[0]); - this.node_dragged.pos[1] = Math.round(this.node_dragged.pos[1]); - if(this.graph.config.align_to_grid) - this.node_dragged.alignToGrid(); - this.node_dragged = null; - } - else //no node being dragged - { - //get node over - var node = this.graph.getNodeOnPos( e.canvasX, e.canvasY, this.visible_nodes ); - if ( !node && e.click_time < 300 ) - this.deselectAllNodes(); - - this.dirty_canvas = true; - this.dragging_canvas = false; - - if( this.node_over && this.node_over.onMouseUp ) - this.node_over.onMouseUp(e, [e.canvasX - this.node_over.pos[0], e.canvasY - this.node_over.pos[1]] ); - if( this.node_capturing_input && this.node_capturing_input.onMouseUp ) - this.node_capturing_input.onMouseUp(e, [e.canvasX - this.node_capturing_input.pos[0], e.canvasY - this.node_capturing_input.pos[1]] ); - } - } - else if (e.which == 2) //middle button - { - //trace("middle"); - this.dirty_canvas = true; - this.dragging_canvas = false; - } - else if (e.which == 3) //right button - { - //trace("right"); - this.dirty_canvas = true; - this.dragging_canvas = false; - } - - /* - if((this.dirty_canvas || this.dirty_bgcanvas) && this.rendering_timer_id == null) - this.draw(); - */ - - this.graph.change(); - - e.stopPropagation(); - e.preventDefault(); - return false; -} - - -LGraphCanvas.prototype.processMouseWheel = function(e) -{ - if(!this.graph || !this.allow_dragcanvas) - return; - - var delta = (e.wheelDeltaY != null ? e.wheelDeltaY : e.detail * -60); - - this.adjustMouseEvent(e); - - var zoom = this.scale; - - if (delta > 0) - zoom *= 1.1; - else if (delta < 0) - zoom *= 1/(1.1); - - this.setZoom( zoom, [ e.localX, e.localY ] ); - - /* - if(this.rendering_timer_id == null) - this.draw(); - */ - - this.graph.change(); - - e.preventDefault(); - return false; // prevent default -} - -LGraphCanvas.prototype.isOverNodeBox = function( node, canvasx, canvasy ) -{ - var title_height = LiteGraph.NODE_TITLE_HEIGHT; - if( isInsideRectangle( canvasx, canvasy, node.pos[0] + 2, node.pos[1] + 2 - title_height, title_height - 4,title_height - 4) ) - return true; - return false; -} - -LGraphCanvas.prototype.isOverNodeInput = function(node, canvasx, canvasy, slot_pos ) -{ - if(node.inputs) - for(var i = 0, l = node.inputs.length; i < l; ++i) - { - var input = node.inputs[i]; - var link_pos = node.getConnectionPos(true,i); - if( isInsideRectangle(canvasx, canvasy, link_pos[0] - 10, link_pos[1] - 5, 20,10) ) - { - if(slot_pos) - { - slot_pos[0] = link_pos[0]; - slot_pos[1] = link_pos[1]; - } - return i; - } - } - return -1; -} - -LGraphCanvas.prototype.processKey = function(e) -{ - if(!this.graph) - return; - - var block_default = false; - //console.log(e); //debug - - if(e.target.localName == "input") - return; - - if(e.type == "keydown") - { - if(e.keyCode == 32) - { - this.dragging_canvas = true; - block_default = true; - } - - //select all Control A - if(e.keyCode == 65 && e.ctrlKey) - { - this.selectNodes(); - block_default = true; - } - - if(e.code == "KeyC" && (e.metaKey || e.ctrlKey) && !e.shiftKey ) //copy - { - if(this.selected_nodes) - { - this.copyToClipboard(); - block_default = true; - } - } - - if(e.code == "KeyV" && (e.metaKey || e.ctrlKey) && !e.shiftKey ) //paste - { - this.pasteFromClipboard(); - } - - //delete or backspace - if(e.keyCode == 46 || e.keyCode == 8) - { - this.deleteSelectedNodes(); - block_default = true; - } - - //collapse - //... - - //TODO - if(this.selected_nodes) - for (var i in this.selected_nodes) - if(this.selected_nodes[i].onKeyDown) - this.selected_nodes[i].onKeyDown(e); - } - else if( e.type == "keyup" ) - { - if(e.keyCode == 32) - this.dragging_canvas = false; - - if(this.selected_nodes) - for (var i in this.selected_nodes) - if(this.selected_nodes[i].onKeyUp) - this.selected_nodes[i].onKeyUp(e); - } - - this.graph.change(); - - if(block_default) - { - e.preventDefault(); - return false; - } -} - -LGraphCanvas.prototype.copyToClipboard = function() -{ - var clipboard_info = { - nodes: [], - links: [] - }; - var index = 0; - var selected_nodes_array = []; - for(var i in this.selected_nodes) - { - var node = this.selected_nodes[i]; - node._relative_id = index; - selected_nodes_array.push( node ); - index += 1; - } - - for(var i = 0; i < selected_nodes_array.length; ++i) - { - var node = selected_nodes_array[i]; - clipboard_info.nodes.push( node.clone().serialize() ); - if(node.inputs && node.inputs.length) - for(var j = 0; j < node.inputs.length; ++j) - { - var input = node.inputs[j]; - if(!input || input.link == null) - continue; - var link_info = this.graph.links[ input.link ]; - if(!link_info) - continue; - var target_node = this.graph.getNodeById( link_info.origin_id ); - if(!target_node || !this.selected_nodes[ target_node.id ] ) //improve this by allowing connections to non-selected nodes - continue; //not selected - clipboard_info.links.push([ target_node._relative_id, j, node._relative_id, link_info.target_slot ]); - } - } - localStorage.setItem( "litegrapheditor_clipboard", JSON.stringify( clipboard_info ) ); -} - -LGraphCanvas.prototype.pasteFromClipboard = function() -{ - var data = localStorage.getItem( "litegrapheditor_clipboard" ); - if(!data) - return; - - //create nodes - var clipboard_info = JSON.parse(data); - var nodes = []; - for(var i = 0; i < clipboard_info.nodes.length; ++i) - { - var node_data = clipboard_info.nodes[i]; - var node = LiteGraph.createNode( node_data.type ); - if(node) - { - node.configure(node_data); - node.pos[0] += 5; - node.pos[1] += 5; - this.graph.add( node ); - nodes.push( node ); - } - } - - //create links - for(var i = 0; i < clipboard_info.links.length; ++i) - { - var link_info = clipboard_info.links[i]; - var origin_node = nodes[ link_info[0] ]; - var target_node = nodes[ link_info[2] ]; - origin_node.connect( link_info[1], target_node, link_info[3] ); - } - - this.selectNodes( nodes ); -} - -LGraphCanvas.prototype.processDrop = function(e) -{ - e.preventDefault(); - this.adjustMouseEvent(e); - - - var pos = [e.canvasX,e.canvasY]; - var node = this.graph.getNodeOnPos(pos[0],pos[1]); - - if(!node) - { - var r = null; - if(this.onDropItem) - r = this.onDropItem( event ); - if(!r) - this.checkDropItem(e); - return; - } - - if( node.onDropFile || node.onDropData ) - { - var files = e.dataTransfer.files; - if(files && files.length) - { - for(var i=0; i < files.length; i++) - { - var file = e.dataTransfer.files[0]; - var filename = file.name; - var ext = LGraphCanvas.getFileExtension( filename ); - //console.log(file); - - if(node.onDropFile) - node.onDropFile(file); - - if(node.onDropData) - { - //prepare reader - var reader = new FileReader(); - reader.onload = function (event) { - //console.log(event.target); - var data = event.target.result; - node.onDropData( data, filename, file ); - }; - - //read data - var type = file.type.split("/")[0]; - if(type == "text" || type == "") - reader.readAsText(file); - else if (type == "image") - reader.readAsDataURL(file); - else - reader.readAsArrayBuffer(file); - } - } - } - } - - if(node.onDropItem) - { - if( node.onDropItem( event ) ) - return true; - } - - if(this.onDropItem) - return this.onDropItem( event ); - - return false; -} - -//called if the graph doesnt have a default drop item behaviour -LGraphCanvas.prototype.checkDropItem = function(e) -{ - if(e.dataTransfer.files.length) - { - var file = e.dataTransfer.files[0]; - var ext = LGraphCanvas.getFileExtension( file.name ).toLowerCase(); - var nodetype = LiteGraph.node_types_by_file_extension[ext]; - if(nodetype) - { - var node = LiteGraph.createNode( nodetype.type ); - node.pos = [e.canvasX, e.canvasY]; - this.graph.add( node ); - if( node.onDropFile ) - node.onDropFile( file ); - } - } -} - - -LGraphCanvas.prototype.processNodeDblClicked = function(n) -{ - if(this.onShowNodePanel) - this.onShowNodePanel(n); - - if(this.onNodeDblClicked) - this.onNodeDblClicked(n); - - this.setDirty(true); -} - -LGraphCanvas.prototype.processNodeSelected = function(node,e) -{ - this.selectNode( node, e && e.shiftKey ); - if(this.onNodeSelected) - this.onNodeSelected(node); -} - -LGraphCanvas.prototype.processNodeDeselected = function(node) -{ - this.deselectNode(node); - if(this.onNodeDeselected) - this.onNodeDeselected(node); -} - -LGraphCanvas.prototype.selectNode = function( node, add_to_current_selection ) -{ - if(node == null) - this.deselectAllNodes(); - else - this.selectNodes([node], add_to_current_selection ); -} - -LGraphCanvas.prototype.selectNodes = function( nodes, add_to_current_selection ) -{ - if(!add_to_current_selection) - this.deselectAllNodes(); - - nodes = nodes || this.graph._nodes; - for(var i = 0; i < nodes.length; ++i) - { - var node = nodes[i]; - if(node.selected) - continue; - - if( !node.selected && node.onSelected ) - node.onSelected(); - node.selected = true; - this.selected_nodes[ node.id ] = node; - - if(node.inputs) - for(var i = 0; i < node.inputs.length; ++i) - this.highlighted_links[ node.inputs[i].link ] = true; - if(node.outputs) - for(var i = 0; i < node.outputs.length; ++i) - { - var out = node.outputs[i]; - if( out.links ) - for(var j = 0; j < out.links.length; ++j) - this.highlighted_links[ out.links[j] ] = true; - } - - } - - this.setDirty(true); -} - -LGraphCanvas.prototype.deselectNode = function( node ) -{ - if(!node.selected) - return; - if(node.onDeselected) - node.onDeselected(); - node.selected = false; - - //remove highlighted - if(node.inputs) - for(var i = 0; i < node.inputs.length; ++i) - delete this.highlighted_links[ node.inputs[i].link ]; - if(node.outputs) - for(var i = 0; i < node.outputs.length; ++i) - { - var out = node.outputs[i]; - if( out.links ) - for(var j = 0; j < out.links.length; ++j) - delete this.highlighted_links[ out.links[j] ]; - } -} - -LGraphCanvas.prototype.deselectAllNodes = function() -{ - if(!this.graph) - return; - var nodes = this.graph._nodes; - for(var i = 0, l = nodes.length; i < l; ++i) - { - var node = nodes[i]; - if(!node.selected) - continue; - if(node.onDeselected) - node.onDeselected(); - node.selected = false; - } - this.selected_nodes = {}; - this.highlighted_links = {}; - this.setDirty(true); -} - -LGraphCanvas.prototype.deleteSelectedNodes = function() -{ - for(var i in this.selected_nodes) - { - var m = this.selected_nodes[i]; - //if(m == this.node_in_panel) this.showNodePanel(null); - this.graph.remove(m); - } - this.selected_nodes = {}; - this.highlighted_links = {}; - this.setDirty(true); -} - -LGraphCanvas.prototype.centerOnNode = function(node) -{ - this.offset[0] = -node.pos[0] - node.size[0] * 0.5 + (this.canvas.width * 0.5 / this.scale); - this.offset[1] = -node.pos[1] - node.size[1] * 0.5 + (this.canvas.height * 0.5 / this.scale); - this.setDirty(true,true); -} - -LGraphCanvas.prototype.adjustMouseEvent = function(e) -{ - if(this.canvas) - { - var b = this.canvas.getBoundingClientRect(); - e.localX = e.pageX - b.left; - e.localY = e.pageY - b.top; - } - else - { - e.localX = e.pageX; - e.localY = e.pageY; - } - - e.deltaX = e.localX - this.last_mouse_position[0]; - e.deltaY = e.localY - this.last_mouse_position[1]; - - this.last_mouse_position[0] = e.localX; - this.last_mouse_position[1] = e.localY; - - e.canvasX = e.localX / this.scale - this.offset[0]; - e.canvasY = e.localY / this.scale - this.offset[1]; -} - -LGraphCanvas.prototype.setZoom = function(value, zooming_center) -{ - if(!zooming_center && this.canvas) - zooming_center = [this.canvas.width * 0.5,this.canvas.height * 0.5]; - - var center = this.convertOffsetToCanvas( zooming_center ); - - this.scale = value; - - if(this.scale > this.max_zoom) - this.scale = this.max_zoom; - else if(this.scale < this.min_zoom) - this.scale = this.min_zoom; - - var new_center = this.convertOffsetToCanvas( zooming_center ); - var delta_offset = [new_center[0] - center[0], new_center[1] - center[1]]; - - this.offset[0] += delta_offset[0]; - this.offset[1] += delta_offset[1]; - - this.dirty_canvas = true; - this.dirty_bgcanvas = true; -} - -LGraphCanvas.prototype.convertOffsetToCanvas = function( pos, out ) -{ - out = out || []; - out[0] = pos[0] / this.scale - this.offset[0]; - out[1] = pos[1] / this.scale - this.offset[1]; - return out; -} - -LGraphCanvas.prototype.convertCanvasToOffset = function( pos, out ) -{ - out = out || []; - out[0] = (pos[0] + this.offset[0]) * this.scale; - out[1] = (pos[1] + this.offset[1]) * this.scale; - return out; -} - -LGraphCanvas.prototype.convertEventToCanvas = function(e) -{ - var rect = this.canvas.getBoundingClientRect(); - return this.convertOffsetToCanvas([e.pageX - rect.left,e.pageY - rect.top]); -} - -LGraphCanvas.prototype.bringToFront = function(n) -{ - var i = this.graph._nodes.indexOf(n); - if(i == -1) return; - - this.graph._nodes.splice(i,1); - this.graph._nodes.push(n); -} - -LGraphCanvas.prototype.sendToBack = function(n) -{ - var i = this.graph._nodes.indexOf(n); - if(i == -1) return; - - this.graph._nodes.splice(i,1); - this.graph._nodes.unshift(n); -} - -/* Interaction */ - - - -/* LGraphCanvas render */ -var temp = new Float32Array(4); - -LGraphCanvas.prototype.computeVisibleNodes = function( nodes, out ) -{ - var visible_nodes = out || []; - visible_nodes.length = 0; - nodes = nodes || this.graph._nodes; - for(var i = 0, l = nodes.length; i < l; ++i) - { - var n = nodes[i]; - - //skip rendering nodes in live mode - if(this.live_mode && !n.onDrawBackground && !n.onDrawForeground) - continue; - - if(!overlapBounding( this.visible_area, n.getBounding( temp ) )) - continue; //out of the visible area - - visible_nodes.push(n); - } - return visible_nodes; -} - -LGraphCanvas.prototype.draw = function(force_canvas, force_bgcanvas) -{ - if(!this.canvas) - return; - - //fps counting - var now = LiteGraph.getTime(); - this.render_time = (now - this.last_draw_time)*0.001; - this.last_draw_time = now; - - if(this.graph) - { - var start = [-this.offset[0], -this.offset[1] ]; - var end = [start[0] + this.canvas.width / this.scale, start[1] + this.canvas.height / this.scale]; - this.visible_area = new Float32Array([ start[0], start[1], end[0] - start[0], end[1] - start[1] ]); - } - - if(this.dirty_bgcanvas || force_bgcanvas || this.always_render_background || (this.graph && this.graph._last_trigger_time && (now - this.graph._last_trigger_time) < 1000) ) - this.drawBackCanvas(); - - if(this.dirty_canvas || force_canvas) - this.drawFrontCanvas(); - - this.fps = this.render_time ? (1.0 / this.render_time) : 0; - this.frame += 1; -} - -LGraphCanvas.prototype.drawFrontCanvas = function() -{ - this.dirty_canvas = false; - - if(!this.ctx) - this.ctx = this.bgcanvas.getContext("2d"); - var ctx = this.ctx; - if(!ctx) //maybe is using webgl... - return; - - if(ctx.start2D) - ctx.start2D(); - - var canvas = this.canvas; - - //reset in case of error - ctx.restore(); - ctx.setTransform(1, 0, 0, 1, 0, 0); - - //clip dirty area if there is one, otherwise work in full canvas - if(this.dirty_area) - { - ctx.save(); - ctx.beginPath(); - ctx.rect(this.dirty_area[0],this.dirty_area[1],this.dirty_area[2],this.dirty_area[3]); - ctx.clip(); - } - - //clear - //canvas.width = canvas.width; - if(this.clear_background) - ctx.clearRect(0,0,canvas.width, canvas.height); - - //draw bg canvas - if(this.bgcanvas == this.canvas) - this.drawBackCanvas(); - else - ctx.drawImage(this.bgcanvas,0,0); - - //rendering - if(this.onRender) - this.onRender(canvas, ctx); - - //info widget - if(this.show_info) - this.renderInfo(ctx); - - if(this.graph) - { - //apply transformations - ctx.save(); - ctx.scale(this.scale,this.scale); - ctx.translate( this.offset[0],this.offset[1] ); - - //draw nodes - var drawn_nodes = 0; - var visible_nodes = this.computeVisibleNodes( null, this.visible_nodes ); - - for (var i = 0; i < visible_nodes.length; ++i) - { - var node = visible_nodes[i]; - - //transform coords system - ctx.save(); - ctx.translate( node.pos[0], node.pos[1] ); - - //Draw - this.drawNode( node, ctx ); - drawn_nodes += 1; - - //Restore - ctx.restore(); - } - - //connections ontop? - if(this.graph.config.links_ontop) - if(!this.live_mode) - this.drawConnections(ctx); - - //current connection - if(this.connecting_pos != null) - { - ctx.lineWidth = this.connections_width; - var link_color = null; - switch( this.connecting_output.type ) - { - case LiteGraph.EVENT: link_color = "#F85"; break; - default: - link_color = "#AFA"; - } - //the connection being dragged by the mouse - this.renderLink( ctx, this.connecting_pos, [this.canvas_mouse[0],this.canvas_mouse[1]], null, false, null, link_color ); - - ctx.beginPath(); - if( this.connecting_output.type === LiteGraph.EVENT || this.connecting_output.shape === LiteGraph.BOX_SHAPE ) - ctx.rect( (this.connecting_pos[0] - 6) + 0.5, (this.connecting_pos[1] - 5) + 0.5,14,10); - else - ctx.arc( this.connecting_pos[0], this.connecting_pos[1],4,0,Math.PI*2); - ctx.fill(); - - ctx.fillStyle = "#ffcc00"; - if(this._highlight_input) - { - ctx.beginPath(); - ctx.arc( this._highlight_input[0], this._highlight_input[1],6,0,Math.PI*2); - ctx.fill(); - } - } - - if( this.dragging_rectangle ) - { - ctx.strokeStyle = "#FFF"; - ctx.strokeRect( this.dragging_rectangle[0], this.dragging_rectangle[1], this.dragging_rectangle[2], this.dragging_rectangle[3] ); - } - - - ctx.restore(); - } - - if(this.dirty_area) - { - ctx.restore(); - //this.dirty_area = null; - } - - if(ctx.finish2D) //this is a function I use in webgl renderer - ctx.finish2D(); -} - -LGraphCanvas.prototype.renderInfo = function( ctx, x, y ) -{ - x = x || 0; - y = y || 0; - - ctx.save(); - ctx.translate( x, y ); - - ctx.font = "10px Arial"; - ctx.fillStyle = "#888"; - if(this.graph) - { - ctx.fillText( "T: " + this.graph.globaltime.toFixed(2)+"s",5,13*1 ); - ctx.fillText( "I: " + this.graph.iteration,5,13*2 ); - ctx.fillText( "V: " + this.graph._version,5,13*3 ); - ctx.fillText( "FPS:" + this.fps.toFixed(2),5,13*4 ); - } - else - ctx.fillText( "No graph selected",5,13*1 ); - ctx.restore(); -} - -LGraphCanvas.prototype.drawBackCanvas = function() -{ - var canvas = this.bgcanvas; - if(canvas.width != this.canvas.width || - canvas.height != this.canvas.height) - { - canvas.width = this.canvas.width; - canvas.height = this.canvas.height; - } - - if(!this.bgctx) - this.bgctx = this.bgcanvas.getContext("2d"); - var ctx = this.bgctx; - if(ctx.start) - ctx.start(); - - //clear - if(this.clear_background) - ctx.clearRect(0,0,canvas.width, canvas.height); - - if(this._graph_stack && this._graph_stack.length) - { - ctx.strokeStyle = this._graph_stack[ this._graph_stack.length - 1].bgcolor; - ctx.lineWidth = 10; - ctx.strokeRect(1,1,canvas.width-2,canvas.height-2); - ctx.lineWidth = 1; - } - - var bg_already_painted = false; - if(this.onRenderBackground) - bg_already_painted = this.onRenderBackground(); - - //reset in case of error - ctx.restore(); - ctx.setTransform(1, 0, 0, 1, 0, 0); - - if(this.graph) - { - //apply transformations - ctx.save(); - ctx.scale(this.scale,this.scale); - ctx.translate(this.offset[0],this.offset[1]); - - //render BG - if(this.background_image && this.scale > 0.5 && !bg_already_painted) - { - if (this.zoom_modify_alpha) - ctx.globalAlpha = (1.0 - 0.5 / this.scale) * this.editor_alpha; - else - ctx.globalAlpha = this.editor_alpha; - ctx.imageSmoothingEnabled = ctx.mozImageSmoothingEnabled = ctx.imageSmoothingEnabled = false; - if(!this._bg_img || this._bg_img.name != this.background_image) - { - this._bg_img = new Image(); - this._bg_img.name = this.background_image; - this._bg_img.src = this.background_image; - var that = this; - this._bg_img.onload = function() { - that.draw(true,true); - } - } - - var pattern = null; - if(this._pattern == null && this._bg_img.width > 0) - { - pattern = ctx.createPattern( this._bg_img, 'repeat' ); - this._pattern_img = this._bg_img; - this._pattern = pattern; - } - else - pattern = this._pattern; - if(pattern) - { - ctx.fillStyle = pattern; - ctx.fillRect(this.visible_area[0],this.visible_area[1],this.visible_area[2],this.visible_area[3]); - ctx.fillStyle = "transparent"; - } - - ctx.globalAlpha = 1.0; - ctx.imageSmoothingEnabled = ctx.mozImageSmoothingEnabled = ctx.imageSmoothingEnabled = true; - } - - //groups - if(this.graph._groups.length && !this.live_mode) - this.drawGroups(canvas, ctx); - - if(this.onBackgroundRender) - this.onBackgroundRender(canvas, ctx); - - //DEBUG: show clipping area - //ctx.fillStyle = "red"; - //ctx.fillRect( this.visible_area[0] + 10, this.visible_area[1] + 10, this.visible_area[2] - 20, this.visible_area[3] - 20); - - //bg - if (this.render_canvas_border) { - ctx.strokeStyle = "#235"; - ctx.strokeRect(0,0,canvas.width,canvas.height); - } - - if(this.render_connections_shadows) - { - ctx.shadowColor = "#000"; - ctx.shadowOffsetX = 0; - ctx.shadowOffsetY = 0; - ctx.shadowBlur = 6; - } - else - ctx.shadowColor = "rgba(0,0,0,0)"; - - //draw connections - if(!this.live_mode) - this.drawConnections(ctx); - - ctx.shadowColor = "rgba(0,0,0,0)"; - - //restore state - ctx.restore(); - } - - if(ctx.finish) - ctx.finish(); - - this.dirty_bgcanvas = false; - this.dirty_canvas = true; //to force to repaint the front canvas with the bgcanvas -} - -var temp_vec2 = new Float32Array(2); - -/* Renders the LGraphNode on the canvas */ -LGraphCanvas.prototype.drawNode = function(node, ctx ) -{ - var glow = false; - this.current_node = node; - - var color = node.color || node.constructor.color || LiteGraph.NODE_DEFAULT_COLOR; - var bgcolor = node.bgcolor || node.constructor.bgcolor || LiteGraph.NODE_DEFAULT_BGCOLOR; - - //shadow and glow - if (node.mouseOver) - glow = true; - - if(node.selected) - { - /* - ctx.shadowColor = "#EEEEFF";//glow ? "#AAF" : "#000"; - ctx.shadowOffsetX = 0; - ctx.shadowOffsetY = 0; - ctx.shadowBlur = 1; - */ - } - else if(this.render_shadows) - { - ctx.shadowColor = "rgba(0,0,0,0.5)"; - ctx.shadowOffsetX = 2; - ctx.shadowOffsetY = 2; - ctx.shadowBlur = 3; - } - else - ctx.shadowColor = "transparent"; - - //only render if it forces it to do it - if(this.live_mode) - { - if(!node.flags.collapsed) - { - ctx.shadowColor = "transparent"; - if(node.onDrawForeground) - node.onDrawForeground(ctx, this); - } - - return; - } - - //custom draw collapsed method - if(node.flags.collapsed && node.onDrawCollaped && node.onDrawCollapsed(ctx, this) == true) - return; - - var editor_alpha = this.editor_alpha; - ctx.globalAlpha = editor_alpha; - - //clip if required (mask) - var shape = node._shape || LiteGraph.BOX_SHAPE; - var size = temp_vec2; - temp_vec2.set( node.size ); - if( node.flags.collapsed ) - { - ctx.font = this.inner_text_font; - var title = node.getTitle ? node.getTitle() : node.title; - node._collapsed_width = Math.min( node.size[0], ctx.measureText(title).width + 40 );//LiteGraph.NODE_COLLAPSED_WIDTH; - size[0] = node._collapsed_width; - size[1] = 0; - } - - if( node.flags.clip_area ) //Start clipping - { - ctx.save(); - ctx.beginPath(); - if(shape == LiteGraph.BOX_SHAPE) - ctx.rect(0,0,size[0], size[1]); - else if (shape == LiteGraph.ROUND_SHAPE) - ctx.roundRect(0,0,size[0], size[1],10); - else if (shape == LiteGraph.CIRCLE_SHAPE) - ctx.arc(size[0] * 0.5, size[1] * 0.5, size[0] * 0.5, 0, Math.PI*2); - ctx.clip(); - } - - //draw shape - this.drawNodeShape( node, ctx, size, color, bgcolor, node.selected, node.mouseOver ); - ctx.shadowColor = "transparent"; - - //connection slots - ctx.textAlign = "left"; - ctx.font = this.inner_text_font; - - var render_text = this.scale > 0.6; - - var out_slot = this.connecting_output; - ctx.lineWidth = 1; - - var max_y = 0; - - //render inputs and outputs - if(!node.flags.collapsed) - { - //input connection slots - if(node.inputs) - for(var i = 0; i < node.inputs.length; i++) - { - var slot = node.inputs[i]; - - ctx.globalAlpha = editor_alpha; - //change opacity of incompatible slots when dragging a connection - if ( this.connecting_node && LiteGraph.isValidConnection( slot.type && out_slot.type ) ) - ctx.globalAlpha = 0.4 * editor_alpha; - - ctx.fillStyle = slot.link != null ? (slot.colorOn || this.default_connection_color.input_on) : (slot.colorOff || this.default_connection_color.input_off); - - var pos = node.getConnectionPos( true, i ); - pos[0] -= node.pos[0]; - pos[1] -= node.pos[1]; - if( max_y < pos[1] + LiteGraph.NODE_SLOT_HEIGHT*0.5 ) - max_y = pos[1] + LiteGraph.NODE_SLOT_HEIGHT*0.5; - - ctx.beginPath(); - - if (slot.type === LiteGraph.EVENT || slot.shape === LiteGraph.BOX_SHAPE) { - ctx.rect((pos[0] - 6) + 0.5, (pos[1] - 5) + 0.5, 14, 10); - } else if (slot.shape === LiteGraph.ARROW_SHAPE) { - ctx.moveTo(pos[0] + 8, pos[1] + 0.5); - ctx.lineTo(pos[0] - 4, (pos[1] + 6) + 0.5); - ctx.lineTo(pos[0] - 4, (pos[1] - 6) + 0.5); - ctx.closePath(); - } else { - ctx.arc(pos[0], pos[1], 4, 0, Math.PI * 2); - } - - ctx.fill(); - - //render name - if(render_text) - { - var text = slot.label != null ? slot.label : slot.name; - if(text) - { - ctx.fillStyle = LiteGraph.NODE_TEXT_COLOR; - ctx.fillText(text,pos[0] + 10,pos[1] + 5); - } - } - } - - //output connection slots - if(this.connecting_node) - ctx.globalAlpha = 0.4 * editor_alpha; - - ctx.textAlign = "right"; - ctx.strokeStyle = "black"; - if(node.outputs) - for(var i = 0; i < node.outputs.length; i++) - { - var slot = node.outputs[i]; - - var pos = node.getConnectionPos(false,i); - pos[0] -= node.pos[0]; - pos[1] -= node.pos[1]; - if( max_y < pos[1] + LiteGraph.NODE_SLOT_HEIGHT*0.5) - max_y = pos[1] + LiteGraph.NODE_SLOT_HEIGHT*0.5; - - ctx.fillStyle = slot.links && slot.links.length ? (slot.colorOn || this.default_connection_color.output_on) : (slot.colorOff || this.default_connection_color.output_off); - ctx.beginPath(); - //ctx.rect( node.size[0] - 14,i*14,10,10); - - if (slot.type === LiteGraph.EVENT || slot.shape === LiteGraph.BOX_SHAPE) { - ctx.rect((pos[0] - 6) + 0.5,(pos[1] - 5) + 0.5,14,10); - } else if (slot.shape === LiteGraph.ARROW_SHAPE) { - ctx.moveTo(pos[0] + 8, pos[1] + 0.5); - ctx.lineTo(pos[0] - 4, (pos[1] + 6) + 0.5); - ctx.lineTo(pos[0] - 4, (pos[1] - 6) + 0.5); - ctx.closePath(); - } else { - ctx.arc(pos[0], pos[1], 4, 0, Math.PI * 2); - } - - //trigger - //if(slot.node_id != null && slot.slot == -1) - // ctx.fillStyle = "#F85"; - - //if(slot.links != null && slot.links.length) - ctx.fill(); - ctx.stroke(); - - //render output name - if(render_text) - { - var text = slot.label != null ? slot.label : slot.name; - if(text) - { - ctx.fillStyle = LiteGraph.NODE_TEXT_COLOR; - ctx.fillText(text, pos[0] - 10,pos[1] + 5); - } - } - } - - ctx.textAlign = "left"; - ctx.globalAlpha = 1; - - if(node.widgets) - this.drawNodeWidgets( node, max_y, ctx, (this.node_widget && this.node_widget[0] == node) ? this.node_widget[1] : null ); - - //draw foreground - if(node.onDrawForeground) - { - //immediate gui stuff - if( node.gui_rects ) - node.gui_rects.length = 0; - node.onDrawForeground( ctx, this ); - } - } - else //if collapsed - { - if(node.inputs) - { - for(var i = 0; i < node.inputs.length; i++) - { - var slot = node.inputs[i]; - if( slot.link == null ) - continue; - ctx.fillStyle = slot.colorOn || this.default_connection_color.input_on; - ctx.beginPath(); - if ( slot.type === LiteGraph.EVENT || slot.shape === LiteGraph.BOX_SHAPE) { - ctx.rect(0.5, 4 - LiteGraph.NODE_TITLE_HEIGHT + 0.5,14,LiteGraph.NODE_TITLE_HEIGHT - 8); - } else if (slot.shape === LiteGraph.ARROW_SHAPE) { - ctx.moveTo(8, LiteGraph.NODE_TITLE_HEIGHT * -0.5); - ctx.lineTo(-4, LiteGraph.NODE_TITLE_HEIGHT * -0.8); - ctx.lineTo(-4, LiteGraph.NODE_TITLE_HEIGHT * -0.2); - ctx.closePath(); - } else { - ctx.arc(0, LiteGraph.NODE_TITLE_HEIGHT * -0.5, 4, 0, Math.PI * 2); - } - ctx.fill(); - break; - } - } - - if(node.outputs) - { - for(var i = 0; i < node.outputs.length; i++) - { - var slot = node.outputs[i]; - if(!slot.links || !slot.links.length) - continue; - ctx.fillStyle = slot.colorOn || this.default_connection_color.output_on; - ctx.strokeStyle = "black"; - ctx.beginPath(); - if (slot.type === LiteGraph.EVENT || slot.shape === LiteGraph.BOX_SHAPE) { - ctx.rect( node._collapsed_width - 4 + 0.5, 4 - LiteGraph.NODE_TITLE_HEIGHT + 0.5,14,LiteGraph.NODE_TITLE_HEIGHT - 8); - } else if (slot.shape === LiteGraph.ARROW_SHAPE) { - ctx.moveTo(node._collapsed_width + 6, LiteGraph.NODE_TITLE_HEIGHT * -0.5); - ctx.lineTo(node._collapsed_width - 6, LiteGraph.NODE_TITLE_HEIGHT * -0.8); - ctx.lineTo(node._collapsed_width - 6, LiteGraph.NODE_TITLE_HEIGHT * -0.2); - ctx.closePath(); - } else { - ctx.arc(node._collapsed_width, LiteGraph.NODE_TITLE_HEIGHT * -0.5, 4, 0, Math.PI * 2); - } - ctx.fill(); - ctx.stroke(); - } - } - - } - - if(node.flags.clip_area) - ctx.restore(); - - ctx.globalAlpha = 1.0; -} - -/* Renders the node shape */ -LGraphCanvas.prototype.drawNodeShape = function( node, ctx, size, fgcolor, bgcolor, selected, mouse_over ) -{ - //bg rect - ctx.strokeStyle = fgcolor; - ctx.fillStyle = bgcolor; - - var title_height = LiteGraph.NODE_TITLE_HEIGHT; - - //render node area depending on shape - var shape = node._shape || node.constructor.shape || LiteGraph.BOX_SHAPE; - var title_mode = node.constructor.title_mode; - - var render_title = true; - if( title_mode == LiteGraph.TRANSPARENT_TITLE ) - render_title = false; - else if( title_mode == LiteGraph.AUTOHIDE_TITLE && mouse_over) - render_title = true; - - var areax = 0; - var areay = render_title ? -title_height : 0; - var areaw = size[0]+1; - var areah = render_title ? size[1] + title_height : size[1]; - - if(!node.flags.collapsed) - { - if(shape == LiteGraph.BOX_SHAPE || this.scale < 0.5) - { - ctx.beginPath(); - ctx.rect( areax, areay, areaw, areah ); - ctx.fill(); - } - else if (shape == LiteGraph.ROUND_SHAPE || shape == LiteGraph.CARD_SHAPE) - { - ctx.beginPath(); - ctx.roundRect( areax, areay, areaw, areah, this.round_radius, shape == LiteGraph.CARD_SHAPE ? 0 : this.round_radius); - ctx.fill(); - } - else if (shape == LiteGraph.CIRCLE_SHAPE) - { - ctx.beginPath(); - ctx.arc(size[0] * 0.5, size[1] * 0.5, size[0] * 0.5, 0, Math.PI*2); - ctx.fill(); - } - } - ctx.shadowColor = "transparent"; - - //image - if (node.bgImage && node.bgImage.width) - ctx.drawImage( node.bgImage, (size[0] - node.bgImage.width) * 0.5 , (size[1] - node.bgImage.height) * 0.5); - - if(node.bgImageUrl && !node.bgImage) - node.bgImage = node.loadImage(node.bgImageUrl); - - if( node.onDrawBackground ) - { - //immediate gui stuff - if( node.gui_rects ) - node.gui_rects.length = 0; - node.onDrawBackground( ctx, this ); - } - - //title bg (remember, it is rendered ABOVE the node) - if(render_title || title_mode == LiteGraph.TRANSPARENT_TITLE ) - { - //title bar - if(title_mode != LiteGraph.TRANSPARENT_TITLE) //!node.flags.collapsed) - { - //* gradient test - if(this.use_gradients) - { - var grad = LGraphCanvas.gradients[ fgcolor ]; - if(!grad) - { - grad = LGraphCanvas.gradients[ fgcolor ] = ctx.createLinearGradient(0,0,400,0); - grad.addColorStop(0, fgcolor); - grad.addColorStop(1, "#000"); - } - ctx.fillStyle = grad; - } - else - ctx.fillStyle = fgcolor; - - var old_alpha = ctx.globalAlpha; - //ctx.globalAlpha = 0.5 * old_alpha; - ctx.beginPath(); - if(shape == LiteGraph.BOX_SHAPE || this.scale < 0.5) - { - ctx.rect(0, -title_height, size[0]+1, title_height); - ctx.fill() - //ctx.stroke(); - } - else if (shape == LiteGraph.ROUND_SHAPE || shape == LiteGraph.CARD_SHAPE) - { - ctx.roundRect(0,-title_height,size[0]+1, title_height, this.round_radius, node.flags.collapsed ? this.round_radius : 0); - ctx.fill(); - } - - /* - else if (shape == LiteGraph.CIRCLE_SHAPE) - { - ctx.beginPath(); - ctx.arc(title_height *0.5, title_height * -0.5, (title_height - 6) *0.5,0,Math.PI*2); - ctx.fill(); - } - */ - } - - //title box - ctx.fillStyle = node.boxcolor || LiteGraph.NODE_DEFAULT_BOXCOLOR; - ctx.beginPath(); - if (shape == LiteGraph.ROUND_SHAPE || shape == LiteGraph.CIRCLE_SHAPE || shape == LiteGraph.CARD_SHAPE) - ctx.arc(title_height *0.5, title_height * -0.5, (title_height - 8) *0.5,0,Math.PI*2); - else - ctx.rect(4,-title_height + 4,title_height - 8,title_height - 8); - ctx.fill(); - ctx.globalAlpha = old_alpha; - - //title text - if( this.scale > 0.5 ) - { - ctx.font = this.title_text_font; - var title = node.getTitle(); - if(title) - { - if(selected) - ctx.fillStyle = "white"; - else - ctx.fillStyle = node.constructor.title_text_color || this.node_title_color; - if( node.flags.collapsed ) - { - ctx.textAlign = "center"; - var measure = ctx.measureText(title); - ctx.fillText( title, title_height + measure.width * 0.5, -title_height * 0.2 ); - ctx.textAlign = "left"; - } - else - { - ctx.textAlign = "left"; - ctx.fillText( title, title_height, -title_height * 0.2 ); - } - } - } - } - - //render selection marker - if(selected) - { - if( title_mode == LiteGraph.TRANSPARENT_TITLE ) - { - areay -= title_height; - areah += title_height; - } - ctx.lineWidth = 1; - ctx.beginPath(); - if(shape == LiteGraph.BOX_SHAPE) - ctx.rect(-6 + areax,-6 + areay, 12 + areaw, 12 + areah ); - else if (shape == LiteGraph.ROUND_SHAPE || (shape == LiteGraph.CARD_SHAPE && node.flags.collapsed) ) - ctx.roundRect(-6 + areax,-6 + areay, 12 + areaw, 12 + areah , this.round_radius * 2); - else if (shape == LiteGraph.CARD_SHAPE) - ctx.roundRect(-6 + areax,-6 + areay, 12 + areaw, 12 + areah , this.round_radius * 2, 2); - else if (shape == LiteGraph.CIRCLE_SHAPE) - ctx.arc(size[0] * 0.5, size[1] * 0.5, size[0] * 0.5 + 6, 0, Math.PI*2); - ctx.strokeStyle = "#DDD"; - ctx.stroke(); - ctx.strokeStyle = fgcolor; - } -} - -//OPTIMIZE THIS: precatch connections position instead of recomputing them every time -LGraphCanvas.prototype.drawConnections = function(ctx) -{ - var now = LiteGraph.getTime(); - - //draw connections - ctx.lineWidth = this.connections_width; - - ctx.fillStyle = "#AAA"; - ctx.strokeStyle = "#AAA"; - ctx.globalAlpha = this.editor_alpha; - //for every node - for (var n = 0, l = this.graph._nodes.length; n < l; ++n) - { - var node = this.graph._nodes[n]; - //for every input (we render just inputs because it is easier as every slot can only have one input) - if(node.inputs && node.inputs.length) - for(var i = 0; i < node.inputs.length; ++i) - { - var input = node.inputs[i]; - if(!input || input.link == null) - continue; - var link_id = input.link; - var link = this.graph.links[ link_id ]; - if(!link) - continue; - - var start_node = this.graph.getNodeById( link.origin_id ); - if(start_node == null) continue; - var start_node_slot = link.origin_slot; - var start_node_slotpos = null; - - if(start_node_slot == -1) - start_node_slotpos = [start_node.pos[0] + 10, start_node.pos[1] + 10]; - else - start_node_slotpos = start_node.getConnectionPos(false, start_node_slot); - - this.renderLink( ctx, start_node_slotpos, node.getConnectionPos(true,i), link ); - - //event triggered rendered on top - if(link && link._last_time && (now - link._last_time) < 1000 ) - { - var f = 2.0 - (now - link._last_time) * 0.002; - var color = "rgba(255,255,255, " + f.toFixed(2) + ")"; - this.renderLink( ctx, start_node_slotpos, node.getConnectionPos(true,i), link, true, f, color ); - } - } - } - ctx.globalAlpha = 1; -} - -LGraphCanvas.prototype.renderLink = function( ctx, a, b, link, skip_border, flow, color ) -{ - if(!this.highquality_render) - { - ctx.beginPath(); - ctx.moveTo(a[0],a[1]); - ctx.lineTo(b[0],b[1]); - ctx.stroke(); - return; - } - - var dist = distance(a,b); - - if(this.render_connections_border && this.scale > 0.6) - ctx.lineWidth = this.connections_width + 4; - - //choose color - if( !color && link ) - color = LGraphCanvas.link_type_colors[ link.type ]; - if(!color) - color = this.default_link_color; - - if( link != null && this.highlighted_links[ link.id ] ) - color = "#FFF"; - - //begin line shape - ctx.beginPath(); - - if(this.render_curved_connections) //splines - { - ctx.moveTo(a[0],a[1]); - ctx.bezierCurveTo(a[0] + dist*0.25, a[1], - b[0] - dist*0.25 , b[1], - b[0] ,b[1] ); - } - else //lines - { - ctx.moveTo(a[0]+10,a[1]); - ctx.lineTo(((a[0]+10) + (b[0]-10))*0.5,a[1]); - ctx.lineTo(((a[0]+10) + (b[0]-10))*0.5,b[1]); - ctx.lineTo(b[0]-10,b[1]); - } - - //rendering the outline of the connection can be a little bit slow - if(this.render_connections_border && this.scale > 0.6 && !skip_border) - { - ctx.strokeStyle = "rgba(0,0,0,0.5)"; - ctx.stroke(); - } - - ctx.lineWidth = this.connections_width; - ctx.fillStyle = ctx.strokeStyle = color; - ctx.stroke(); - //end line shape - - //render arrow in the middle - if( this.render_connection_arrows && this.scale >= 0.6 ) - { - //render arrow - if(this.render_connection_arrows && this.scale > 0.6) - { - //compute two points in the connection - var pos = this.computeConnectionPoint(a,b,0.5); - var pos2 = this.computeConnectionPoint(a,b,0.51); - - //compute the angle between them so the arrow points in the right direction - var angle = 0; - if(this.render_curved_connections) - angle = -Math.atan2( pos2[0] - pos[0], pos2[1] - pos[1]); - else - angle = b[1] > a[1] ? 0 : Math.PI; - - //render arrow - ctx.save(); - ctx.translate(pos[0],pos[1]); - ctx.rotate(angle); - ctx.beginPath(); - ctx.moveTo(-5,-5); - ctx.lineTo(0,+5); - ctx.lineTo(+5,-5); - ctx.fill(); - ctx.restore(); - } - } - - //render flowing points - if(flow) - { - for(var i = 0; i < 5; ++i) - { - var f = (LiteGraph.getTime() * 0.001 + (i * 0.2)) % 1; - var pos = this.computeConnectionPoint(a,b,f); - ctx.beginPath(); - ctx.arc(pos[0],pos[1],5,0,2*Math.PI); - ctx.fill(); - } - } -} - -LGraphCanvas.prototype.computeConnectionPoint = function(a,b,t) -{ - var dist = distance(a,b); - var p0 = a; - var p1 = [ a[0] + dist*0.25, a[1] ]; - var p2 = [ b[0] - dist*0.25, b[1] ]; - var p3 = b; - - var c1 = (1-t)*(1-t)*(1-t); - var c2 = 3*((1-t)*(1-t))*t; - var c3 = 3*(1-t)*(t*t); - var c4 = t*t*t; - - var x = c1*p0[0] + c2*p1[0] + c3*p2[0] + c4*p3[0]; - var y = c1*p0[1] + c2*p1[1] + c3*p2[1] + c4*p3[1]; - return [x,y]; -} - -LGraphCanvas.prototype.drawNodeWidgets = function( node, posY, ctx, active_widget ) -{ - if(!node.widgets || !node.widgets.length) - return 0; - var width = node.size[0]; - var widgets = node.widgets; - posY += 2; - var H = LiteGraph.NODE_WIDGET_HEIGHT; - var show_text = this.scale > 0.5; - - for(var i = 0; i < widgets.length; ++i) - { - var w = widgets[i]; - var y = posY; - if(w.y) - y = w.y; - w.last_y = y; - ctx.strokeStyle = "#AAA"; - ctx.fillStyle = "#222"; - ctx.textAlign = "left"; - - switch( w.type ) - { - case "button": - if(w.clicked) - { - ctx.fillStyle = "#AAA"; - w.clicked = false; - this.dirty_canvas = true; - } - ctx.fillRect(10,y,width-20,H); - ctx.strokeRect(10,y,width-20,H); - if(show_text) - { - ctx.textAlign = "center"; - ctx.fillStyle = "#AAA"; - ctx.fillText( w.name, width*0.5, y + H*0.7 ); - } - break; - case "slider": - ctx.fillStyle = "#111"; - ctx.fillRect(10,y,width-20,H); - var range = w.options.max - w.options.min; - var nvalue = (w.value - w.options.min) / range; - ctx.fillStyle = active_widget == w ? "#89A" : "#678"; - ctx.fillRect(10,y,nvalue*(width-20),H); - ctx.strokeRect(10,y,width-20,H); - if(show_text) - { - ctx.textAlign = "center"; - ctx.fillStyle = "#DDD"; - ctx.fillText( w.name + " " + Number(w.value).toFixed(3), width*0.5, y + H*0.7 ); - } - break; - case "number": - case "combo": - ctx.textAlign = "left"; - ctx.strokeStyle = "#AAA"; - ctx.fillStyle = "#111"; - ctx.beginPath(); - ctx.roundRect( 10, posY, width - 20, H,H*0.5 ); - ctx.fill(); - ctx.stroke(); - ctx.fillStyle = "#AAA"; - ctx.beginPath(); - ctx.moveTo( 26, posY + 5 ); - ctx.lineTo( 16, posY + H*0.5 ); - ctx.lineTo( 26, posY + H - 5 ); - ctx.moveTo( width - 26, posY + 5 ); - ctx.lineTo( width - 16, posY + H*0.5 ); - ctx.lineTo( width - 26, posY + H - 5 ); - ctx.fill(); - if(show_text) - { - ctx.fillStyle = "#999"; - ctx.fillText( w.name, 30, y + H*0.7 ); - ctx.fillStyle = "#DDD"; - ctx.textAlign = "right"; - if(w.type == "number") - ctx.fillText( Number(w.value).toFixed( w.options.precision !== undefined ? w.options.precision : 3), width - 40, y + H*0.7 ); - else - ctx.fillText( w.value, width - 40, y + H*0.7 ); - } - break; - default: - break; - } - posY += H + 4; - } - ctx.textAlign = "left"; -} - -LGraphCanvas.prototype.processNodeWidgets = function( node, pos, event, active_widget ) -{ - if(!node.widgets || !node.widgets.length) - return null; - - var x = pos[0] - node.pos[0]; - var y = pos[1] - node.pos[1]; - var width = node.size[0]; - var that = this; - - for(var i = 0; i < node.widgets.length; ++i) - { - var w = node.widgets[i]; - if( w == active_widget || (x > 6 && x < (width - 12) && y > w.last_y && y < (w.last_y + LiteGraph.NODE_WIDGET_HEIGHT)) ) - { - //inside widget - switch( w.type ) - { - case "button": - if(w.callback) - setTimeout( function(){ w.callback( w, that, node, pos ); }, 20 ); - w.clicked = true; - this.dirty_canvas = true; - break; - case "slider": - var range = w.options.max - w.options.min; - var nvalue = Math.clamp( (x - 10) / (width - 20), 0, 1); - w.value = w.options.min + (w.options.max - w.options.min) * nvalue; - if(w.callback) - setTimeout( function(){ w.callback( w.value, that, node, pos ); }, 20 ); - this.dirty_canvas = true; - break; - case "number": - case "combo": - if(event.type == "mousemove" && w.type == "number") - { - w.value += (event.deltaX * 0.1) * (w.options.step || 1); - if(w.options.min != null && w.value < w.options.min) - w.value = w.options.min; - if(w.options.max != null && w.value > w.options.max) - w.value = w.options.max; - } - else if( event.type == "mousedown" ) - { - var delta = ( x < 40 ? -1 : ( x > width - 40 ? 1 : 0) ); - if (w.type == "number") - { - w.value += delta * 0.1 * (w.options.step || 1); - if(w.options.min != null && w.value < w.options.min) - w.value = w.options.min; - if(w.options.max != null && w.value > w.options.max) - w.value = w.options.max; - } - else if(delta) - { - var index = w.options.values.indexOf( w.value ) + delta; - if( index >= w.options.values.length ) - index = 0; - if( index < 0 ) - index = w.options.values.length - 1; - w.value = w.options.values[ index ]; - } - } - if(w.callback) - setTimeout( function(){ w.callback( w.value, that, node, pos ); }, 20 ); - this.dirty_canvas = true; - break; - } - - return w; - } - } - return null; -} - - -LGraphCanvas.prototype.drawGroups = function(canvas, ctx) -{ - if(!this.graph) - return; - - var groups = this.graph._groups; - - ctx.save(); - ctx.globalAlpha = 0.5; - ctx.font = "24px Arial"; - - for(var i = 0; i < groups.length; ++i) - { - var group = groups[i]; - - if(!overlapBounding( this.visible_area, group._bounding )) - continue; //out of the visible area - - ctx.fillStyle = group.color || "#335"; - ctx.strokeStyle = group.color || "#335"; - var pos = group._pos; - var size = group._size; - ctx.globalAlpha = 0.25; - ctx.beginPath(); - ctx.rect( pos[0] + 0.5, pos[1] + 0.5, size[0], size[1] ); - ctx.fill(); - ctx.globalAlpha = 1; - ctx.stroke(); - - ctx.beginPath(); - ctx.moveTo( pos[0] + size[0], pos[1] + size[1] ); - ctx.lineTo( pos[0] + size[0] - 10, pos[1] + size[1] ); - ctx.lineTo( pos[0] + size[0], pos[1] + size[1] - 10 ); - ctx.fill(); - - ctx.fillText( group.title, pos[0] + 4, pos[1] + 24 ); - } - - ctx.restore(); -} - - -LGraphCanvas.prototype.resize = function(width, height) -{ - if(!width && !height) - { - var parent = this.canvas.parentNode; - width = parent.offsetWidth; - height = parent.offsetHeight; - } - - if(this.canvas.width == width && this.canvas.height == height) - return; - - this.canvas.width = width; - this.canvas.height = height; - this.bgcanvas.width = this.canvas.width; - this.bgcanvas.height = this.canvas.height; - this.setDirty(true,true); -} - - -LGraphCanvas.prototype.switchLiveMode = function(transition) -{ - if(!transition) - { - this.live_mode = !this.live_mode; - this.dirty_canvas = true; - this.dirty_bgcanvas = true; - return; - } - - var self = this; - var delta = this.live_mode ? 1.1 : 0.9; - if(this.live_mode) - { - this.live_mode = false; - this.editor_alpha = 0.1; - } - - var t = setInterval(function() { - self.editor_alpha *= delta; - self.dirty_canvas = true; - self.dirty_bgcanvas = true; - - if(delta < 1 && self.editor_alpha < 0.01) - { - clearInterval(t); - if(delta < 1) - self.live_mode = true; - } - if(delta > 1 && self.editor_alpha > 0.99) - { - clearInterval(t); - self.editor_alpha = 1; - } - },1); -} - -LGraphCanvas.prototype.onNodeSelectionChange = function(node) -{ - return; //disabled - //if(this.node_in_panel) this.showNodePanel(node); -} - -LGraphCanvas.prototype.touchHandler = function(event) -{ - //alert("foo"); - var touches = event.changedTouches, - first = touches[0], - type = ""; - - switch(event.type) - { - case "touchstart": type = "mousedown"; break; - case "touchmove": type = "mousemove"; break; - case "touchend": type = "mouseup"; break; - default: return; - } - - //initMouseEvent(type, canBubble, cancelable, view, clickCount, - // screenX, screenY, clientX, clientY, ctrlKey, - // altKey, shiftKey, metaKey, button, relatedTarget); - - var window = this.getCanvasWindow(); - var document = window.document; - - var simulatedEvent = document.createEvent("MouseEvent"); - simulatedEvent.initMouseEvent(type, true, true, window, 1, - first.screenX, first.screenY, - first.clientX, first.clientY, false, - false, false, false, 0/*left*/, null); - first.target.dispatchEvent(simulatedEvent); - event.preventDefault(); -} - -/* CONTEXT MENU ********************/ - -LGraphCanvas.onGroupAdd = function(info,entry,mouse_event) -{ - var canvas = LGraphCanvas.active_canvas; - var ref_window = canvas.getCanvasWindow(); - - var group = new LiteGraph.LGraphGroup(); - group.pos = canvas.convertEventToCanvas( mouse_event ); - canvas.graph.add( group ); -} - -LGraphCanvas.onMenuAdd = function( node, options, e, prev_menu ) -{ - var canvas = LGraphCanvas.active_canvas; - var ref_window = canvas.getCanvasWindow(); - - var values = LiteGraph.getNodeTypesCategories(); - var entries = []; - for(var i in values) - if(values[i]) - entries.push({ value: values[i], content: values[i], has_submenu: true }); - - var menu = new LiteGraph.ContextMenu( entries, { event: e, callback: inner_clicked, parentMenu: prev_menu }, ref_window); - - function inner_clicked( v, option, e ) - { - var category = v.value; - var node_types = LiteGraph.getNodeTypesInCategory( category, canvas.filter ); - var values = []; - for(var i in node_types) - values.push( { content: node_types[i].title, value: node_types[i].type }); - - new LiteGraph.ContextMenu( values, {event: e, callback: inner_create, parentMenu: menu }, ref_window); - return false; - } - - function inner_create( v, e ) - { - var first_event = prev_menu.getFirstEvent(); - var node = LiteGraph.createNode( v.value ); - if(node) - { - node.pos = canvas.convertEventToCanvas( first_event ); - canvas.graph.add( node ); - } - } - - return false; -} - -LGraphCanvas.onMenuCollapseAll = function() -{ - -} - - -LGraphCanvas.onMenuNodeEdit = function() -{ - -} - -LGraphCanvas.showMenuNodeOptionalInputs = function( v, options, e, prev_menu, node ) -{ - if(!node) - return; - - var that = this; - var canvas = LGraphCanvas.active_canvas; - var ref_window = canvas.getCanvasWindow(); - - var options = node.optional_inputs; - if(node.onGetInputs) - options = node.onGetInputs(); - - var entries = []; - if(options) - for (var i in options) - { - var entry = options[i]; - if(!entry) - { - entries.push(null); - continue; - } - var label = entry[0]; - if(entry[2] && entry[2].label) - label = entry[2].label; - var data = {content: label, value: entry}; - if(entry[1] == LiteGraph.ACTION) - data.className = "event"; - entries.push(data); - } - - if(this.onMenuNodeInputs) - entries = this.onMenuNodeInputs( entries ); - - if(!entries.length) - return; - - var menu = new LiteGraph.ContextMenu(entries, { event: e, callback: inner_clicked, parentMenu: prev_menu, node: node }, ref_window); - - function inner_clicked(v, e, prev) - { - if(!node) - return; - - if(v.callback) - v.callback.call( that, node, v, e, prev ); - - if(v.value) - { - node.addInput(v.value[0],v.value[1], v.value[2]); - node.setDirtyCanvas(true,true); - } - } - - return false; -} - -LGraphCanvas.showMenuNodeOptionalOutputs = function( v, options, e, prev_menu, node ) -{ - if(!node) - return; - - var that = this; - var canvas = LGraphCanvas.active_canvas; - var ref_window = canvas.getCanvasWindow(); - - var options = node.optional_outputs; - if(node.onGetOutputs) - options = node.onGetOutputs(); - - var entries = []; - if(options) - for (var i in options) - { - var entry = options[i]; - if(!entry) //separator? - { - entries.push(null); - continue; - } - - if(node.flags && node.flags.skip_repeated_outputs && node.findOutputSlot(entry[0]) != -1) - continue; //skip the ones already on - var label = entry[0]; - if(entry[2] && entry[2].label) - label = entry[2].label; - var data = {content: label, value: entry}; - if(entry[1] == LiteGraph.EVENT) - data.className = "event"; - entries.push(data); - } - - if(this.onMenuNodeOutputs) - entries = this.onMenuNodeOutputs( entries ); - - if(!entries.length) - return; - - var menu = new LiteGraph.ContextMenu(entries, {event: e, callback: inner_clicked, parentMenu: prev_menu, node: node }, ref_window); - - function inner_clicked( v, e, prev ) - { - if(!node) - return; - - if(v.callback) - v.callback.call( that, node, v, e, prev ); - - if(!v.value) - return; - - var value = v.value[1]; - - if(value && (value.constructor === Object || value.constructor === Array)) //submenu why? - { - var entries = []; - for(var i in value) - entries.push({ content: i, value: value[i]}); - new LiteGraph.ContextMenu( entries, { event: e, callback: inner_clicked, parentMenu: prev_menu, node: node }); - return false; - } - else - { - node.addOutput( v.value[0], v.value[1], v.value[2]); - node.setDirtyCanvas(true,true); - } - - } - - return false; -} - -LGraphCanvas.onShowMenuNodeProperties = function( value, options, e, prev_menu, node ) -{ - if(!node || !node.properties) - return; - - var that = this; - var canvas = LGraphCanvas.active_canvas; - var ref_window = canvas.getCanvasWindow(); - - var entries = []; - for (var i in node.properties) - { - var value = node.properties[i] !== undefined ? node.properties[i] : " "; - //value could contain invalid html characters, clean that - value = LGraphCanvas.decodeHTML(value); - entries.push({content: "" + i + "" + "" + value + "", value: i}); - } - if(!entries.length) - return; - - var menu = new LiteGraph.ContextMenu(entries, {event: e, callback: inner_clicked, parentMenu: prev_menu, allow_html: true, node: node },ref_window); - - function inner_clicked( v, options, e, prev ) - { - if(!node) - return; - var rect = this.getBoundingClientRect(); - canvas.showEditPropertyValue( node, v.value, { position: [rect.left, rect.top] }); - } - - return false; -} - -LGraphCanvas.decodeHTML = function( str ) -{ - var e = document.createElement("div"); - e.innerText = str; - return e.innerHTML; -} - -LGraphCanvas.onResizeNode = function( value, options, e, menu, node ) -{ - if(!node) - return; - node.size = node.computeSize(); - node.setDirtyCanvas(true,true); -} - - -LGraphCanvas.onShowTitleEditor = function( value, options, e, menu, node ) -{ - var input_html = ""; - - var dialog = document.createElement("div"); - dialog.className = "graphdialog"; - dialog.innerHTML = "Title"; - var input = dialog.querySelector("input"); - if(input) - { - input.value = node.title; - input.addEventListener("keydown", function(e){ - if(e.keyCode != 13) - return; - inner(); - e.preventDefault(); - e.stopPropagation(); - }); - } - - var graphcanvas = LGraphCanvas.active_canvas; - var canvas = graphcanvas.canvas; - - var rect = canvas.getBoundingClientRect(); - var offsetx = -20; - var offsety = -20; - if(rect) - { - offsetx -= rect.left; - offsety -= rect.top; - } - - if( event ) - { - dialog.style.left = (event.pageX + offsetx) + "px"; - dialog.style.top = (event.pageY + offsety)+ "px"; - } - else - { - dialog.style.left = (canvas.width * 0.5 + offsetx) + "px"; - dialog.style.top = (canvas.height * 0.5 + offsety) + "px"; - } - - var button = dialog.querySelector("button"); - button.addEventListener("click", inner ); - canvas.parentNode.appendChild( dialog ); - - function inner() - { - setValue( input.value ); - } - - function setValue(value) - { - node.title = value; - dialog.parentNode.removeChild( dialog ); - node.setDirtyCanvas(true,true); - } -} - -LGraphCanvas.prototype.showSearchBox = function(event) -{ - var that = this; - var input_html = ""; - - var dialog = document.createElement("div"); - dialog.className = "litegraph litesearchbox"; - dialog.innerHTML = "Search
"; - dialog.close = function() - { - that.search_box = null; - dialog.parentNode.removeChild( dialog ); - } - - dialog.addEventListener("mouseleave",function(e){ - dialog.close(); - }); - - if(that.search_box) - that.search_box.close(); - that.search_box = dialog; - - var helper = dialog.querySelector(".helper"); - - var first = null; - var timeout = null; - var selected = null; - - var input = dialog.querySelector("input"); - if(input) - { - input.addEventListener("keydown", function(e){ - - if(e.keyCode == 38) //UP - changeSelection(false); - else if(e.keyCode == 40) //DOWN - changeSelection(true); - else if(e.keyCode == 27) //ESC - dialog.close(); - else if(e.keyCode == 13) - { - if(selected) - select( selected.innerHTML ) - else if(first) - select(first); - else - dialog.close(); - } - else - { - if(timeout) - clearInterval(timeout); - timeout = setTimeout( refreshHelper, 10 ); - return; - } - e.preventDefault(); - e.stopPropagation(); - }); - } - - var graphcanvas = LGraphCanvas.active_canvas; - var canvas = graphcanvas.canvas; - - var rect = canvas.getBoundingClientRect(); - var offsetx = -20; - var offsety = -20; - if(rect) - { - offsetx -= rect.left; - offsety -= rect.top; - } - - if( event ) - { - dialog.style.left = (event.pageX + offsetx) + "px"; - dialog.style.top = (event.pageY + offsety)+ "px"; - } - else - { - dialog.style.left = (canvas.width * 0.5 + offsetx) + "px"; - dialog.style.top = (canvas.height * 0.5 + offsety) + "px"; - } - - canvas.parentNode.appendChild( dialog ); - input.focus(); - - function select( name ) - { - if(name) - { - if( that.onSearchBoxSelection ) - that.onSearchBoxSelection( name, event, graphcanvas ); - else - { - var node = LiteGraph.createNode( name ); - if(node) - { - node.pos = graphcanvas.convertEventToCanvas( event ); - graphcanvas.graph.add( node ); - } - } - } - - dialog.close(); - } - - function changeSelection( forward ) - { - var prev = selected; - if(selected) - selected.classList.remove("selected"); - if(!selected) - selected = forward ? helper.childNodes[0] : helper.childNodes[ helper.childNodes.length ]; - else - { - selected = forward ? selected.nextSibling : selected.previousSibling; - if(!selected) - selected = prev; - } - if(!selected) - return; - selected.classList.add("selected"); - selected.scrollIntoView(); - } - - function refreshHelper() - { - timeout = null; - var str = input.value; - first = null; - helper.innerHTML = ""; - if(!str) - return; - - if( that.onSearchBox ) - that.onSearchBox( help, str, graphcanvas ); - else - for( var i in LiteGraph.registered_node_types ) - if(i.indexOf(str) != -1) - { - var help = document.createElement("div"); - if(!first) first = i; - help.innerText = i; - help.className = "litegraph lite-search-item"; - help.addEventListener("click", function(e){ - select( this.innerText ); - }); - helper.appendChild(help); - } - } - - return dialog; -} - -LGraphCanvas.prototype.showEditPropertyValue = function( node, property, options ) -{ - if(!node || node.properties[ property ] === undefined ) - return; - - options = options || {}; - var that = this; - - var type = "string"; - - if(node.properties[ property ] !== null) - type = typeof(node.properties[ property ]); - - //for arrays - if(type == "object") - { - if( node.properties[ property ].length ) - type = "array"; - } - - var info = null; - if(node.getPropertyInfo) - info = node.getPropertyInfo(property); - if(node.properties_info) - { - for(var i = 0; i < node.properties_info.length; ++i) - { - if( node.properties_info[i].name == property ) - { - info = node.properties_info[i]; - break; - } - } - } - - if(info !== undefined && info !== null && info.type ) - type = info.type; - - var input_html = ""; - - if(type == "string" || type == "number" || type == "array") - input_html = ""; - else if(type == "enum" && info.values) - { - input_html = ""; - } - else if(type == "boolean") - { - input_html = ""; - } - else - { - console.warn("unknown type: " + type ); - return; - } - - var dialog = this.createDialog( "" + property + ""+input_html+"" , options ); - - if(type == "enum" && info.values) - { - var input = dialog.querySelector("select"); - input.addEventListener("change", function(e){ - setValue( e.target.value ); - //var index = e.target.value; - //setValue( e.options[e.selectedIndex].value ); - }); - } - else if(type == "boolean") - { - var input = dialog.querySelector("input"); - if(input) - { - input.addEventListener("click", function(e){ - setValue( !!input.checked ); - }); - } - } - else - { - var input = dialog.querySelector("input"); - if(input) - { - input.value = node.properties[ property ] !== undefined ? node.properties[ property ] : ""; - input.addEventListener("keydown", function(e){ - if(e.keyCode != 13) - return; - inner(); - e.preventDefault(); - e.stopPropagation(); - }); - } - } - - var button = dialog.querySelector("button"); - button.addEventListener("click", inner ); - - function inner() - { - setValue( input.value ); - } - - function setValue(value) - { - if(typeof( node.properties[ property ] ) == "number") - value = Number(value); - if(type == "array") - value = value.split(",").map(Number); - node.properties[ property ] = value; - if(node._graph) - node._graph._version++; - if(node.onPropertyChanged) - node.onPropertyChanged( property, value ); - dialog.close(); - node.setDirtyCanvas(true,true); - } -} - -LGraphCanvas.prototype.createDialog = function( html, options ) -{ - options = options || {}; - - var dialog = document.createElement("div"); - dialog.className = "graphdialog"; - dialog.innerHTML = html; - - var rect = this.canvas.getBoundingClientRect(); - var offsetx = -20; - var offsety = -20; - if(rect) - { - offsetx -= rect.left; - offsety -= rect.top; - } - - if( options.position ) - { - offsetx += options.position[0]; - offsety += options.position[1]; - } - else if( options.event ) - { - offsetx += options.event.pageX; - offsety += options.event.pageY; - } - else //centered - { - offsetx += this.canvas.width * 0.5; - offsety += this.canvas.height * 0.5; - } - - dialog.style.left = offsetx + "px"; - dialog.style.top = offsety + "px"; - - this.canvas.parentNode.appendChild( dialog ); - - dialog.close = function() - { - if(this.parentNode) - this.parentNode.removeChild( this ); - } - - return dialog; -} - -LGraphCanvas.onMenuNodeCollapse = function( value, options, e, menu, node ) -{ - node.collapse(); -} - -LGraphCanvas.onMenuNodePin = function( value, options, e, menu, node ) -{ - node.pin(); -} - -LGraphCanvas.onMenuNodeMode = function( value, options, e, menu, node ) -{ - new LiteGraph.ContextMenu(["Always","On Event","On Trigger","Never"], {event: e, callback: inner_clicked, parentMenu: menu, node: node }); - - function inner_clicked(v) - { - if(!node) - return; - switch(v) - { - case "On Event": node.mode = LiteGraph.ON_EVENT; break; - case "On Trigger": node.mode = LiteGraph.ON_TRIGGER; break; - case "Never": node.mode = LiteGraph.NEVER; break; - case "Always": - default: - node.mode = LiteGraph.ALWAYS; break; - } - } - - return false; -} - -LGraphCanvas.onMenuNodeColors = function( value, options, e, menu, node ) -{ - if(!node) - throw("no node for color"); - - var values = []; - values.push({ value:null, content:"No color" }); - - for(var i in LGraphCanvas.node_colors) - { - var color = LGraphCanvas.node_colors[i]; - var value = { value:i, content:""+i+"" }; - values.push(value); - } - new LiteGraph.ContextMenu( values, { event: e, callback: inner_clicked, parentMenu: menu, node: node }); - - function inner_clicked(v) - { - if(!node) - return; - - var color = v.value ? LGraphCanvas.node_colors[ v.value ] : null; - if(color) - { - if(node.constructor === LiteGraph.LGraphGroup) - node.color = color.groupcolor; - else - { - node.color = color.color; - node.bgcolor = color.bgcolor; - } - } - else - { - delete node.color; - delete node.bgcolor; - } - node.setDirtyCanvas(true,true); - } - - return false; -} - -LGraphCanvas.onMenuNodeShapes = function( value, options, e, menu, node ) -{ - if(!node) - throw("no node passed"); - - new LiteGraph.ContextMenu( LiteGraph.VALID_SHAPES, { event: e, callback: inner_clicked, parentMenu: menu, node: node }); - - function inner_clicked(v) - { - if(!node) - return; - node.shape = v; - node.setDirtyCanvas(true); - } - - return false; -} - -LGraphCanvas.onMenuNodeRemove = function( value, options, e, menu, node ) -{ - if(!node) - throw("no node passed"); - - if(node.removable === false) - return; - - node.graph.remove(node); - node.setDirtyCanvas(true,true); -} - -LGraphCanvas.onMenuNodeClone = function( value, options, e, menu, node ) -{ - if(node.clonable == false) return; - var newnode = node.clone(); - if(!newnode) - return; - newnode.pos = [node.pos[0]+5,node.pos[1]+5]; - node.graph.add(newnode); - node.setDirtyCanvas(true,true); -} - -LGraphCanvas.node_colors = { - "red": { color:"#322", bgcolor:"#533", groupcolor: "#A88" }, - "brown": { color:"#332922", bgcolor:"#593930", groupcolor: "#b06634" }, - "green": { color:"#232", bgcolor:"#353", groupcolor: "#8A8" }, - "blue": { color:"#223", bgcolor:"#335", groupcolor: "#88A" }, - "pale_blue": { color:"#2a363b", bgcolor:"#3f5159", groupcolor: "#3f789e" }, - "cyan": { color:"#233", bgcolor:"#355", groupcolor: "#8AA" }, - "purple": { color:"#323", bgcolor:"#535", groupcolor: "#a1309b" }, - "yellow": { color:"#432", bgcolor:"#653", groupcolor: "#b58b2a" }, - "black": { color:"#222", bgcolor:"#000", groupcolor: "#444" } -}; - -LGraphCanvas.prototype.getCanvasMenuOptions = function() -{ - var options = null; - if(this.getMenuOptions) - options = this.getMenuOptions(); - else - { - options = [ - { content:"Add Node", has_submenu: true, callback: LGraphCanvas.onMenuAdd }, - { content:"Add Group", callback: LGraphCanvas.onGroupAdd } - //{content:"Collapse All", callback: LGraphCanvas.onMenuCollapseAll } - ]; - - if(this._graph_stack && this._graph_stack.length > 0) - options = [{content:"Close subgraph", callback: this.closeSubgraph.bind(this) },null].concat(options); - } - - if(this.getExtraMenuOptions) - { - var extra = this.getExtraMenuOptions(this,options); - if(extra) - options = options.concat( extra ); - } - - return options; -} - -//called by processContextMenu to extract the menu list -LGraphCanvas.prototype.getNodeMenuOptions = function( node ) -{ - var options = null; - - if(node.getMenuOptions) - options = node.getMenuOptions(this); - else - options = [ - {content:"Inputs", has_submenu: true, disabled:true, callback: LGraphCanvas.showMenuNodeOptionalInputs }, - {content:"Outputs", has_submenu: true, disabled:true, callback: LGraphCanvas.showMenuNodeOptionalOutputs }, - null, - {content:"Properties", has_submenu: true, callback: LGraphCanvas.onShowMenuNodeProperties }, - null, - {content:"Title", callback: LGraphCanvas.onShowTitleEditor }, - {content:"Mode", has_submenu: true, callback: LGraphCanvas.onMenuNodeMode }, - {content:"Resize", callback: LGraphCanvas.onResizeNode }, - {content:"Collapse", callback: LGraphCanvas.onMenuNodeCollapse }, - {content:"Pin", callback: LGraphCanvas.onMenuNodePin }, - {content:"Colors", has_submenu: true, callback: LGraphCanvas.onMenuNodeColors }, - {content:"Shapes", has_submenu: true, callback: LGraphCanvas.onMenuNodeShapes }, - null - ]; - - if(node.getExtraMenuOptions) - { - var extra = node.getExtraMenuOptions(this); - if(extra) - { - extra.push(null); - options = extra.concat( options ); - } - } - - if( node.clonable !== false ) - options.push({content:"Clone", callback: LGraphCanvas.onMenuNodeClone }); - if( node.removable !== false ) - options.push(null,{content:"Remove", callback: LGraphCanvas.onMenuNodeRemove }); - - if(node.onGetInputs) - { - var inputs = node.onGetInputs(); - if(inputs && inputs.length) - options[0].disabled = false; - } - - if(node.onGetOutputs) - { - var outputs = node.onGetOutputs(); - if(outputs && outputs.length ) - options[1].disabled = false; - } - - if(node.graph && node.graph.onGetNodeMenuOptions ) - node.graph.onGetNodeMenuOptions( options, node ); - - return options; -} - -LGraphCanvas.prototype.getGroupMenuOptions = function( node ) -{ - var o = [ - {content:"Title", callback: LGraphCanvas.onShowTitleEditor }, - {content:"Color", has_submenu: true, callback: LGraphCanvas.onMenuNodeColors }, - null, - {content:"Remove", callback: LGraphCanvas.onMenuNodeRemove } - ]; - - return o; -} - -LGraphCanvas.prototype.processContextMenu = function( node, event ) -{ - var that = this; - var canvas = LGraphCanvas.active_canvas; - var ref_window = canvas.getCanvasWindow(); - - var menu_info = null; - var options = { event: event, callback: inner_option_clicked, node: node }; - - //check if mouse is in input - var slot = null; - if(node) - { - slot = node.getSlotInPosition( event.canvasX, event.canvasY ); - LGraphCanvas.active_node = node; - } - - if(slot) //on slot - { - menu_info = []; - menu_info.push( slot.locked ? "Cannot remove" : { content: "Remove Slot", slot: slot } ); - menu_info.push( { content: "Rename Slot", slot: slot } ); - options.title = (slot.input ? slot.input.type : slot.output.type) || "*"; - if(slot.input && slot.input.type == LiteGraph.ACTION) - options.title = "Action"; - if(slot.output && slot.output.type == LiteGraph.EVENT) - options.title = "Event"; - } - else - { - if( node ) //on node - menu_info = this.getNodeMenuOptions(node); - else - { - var group = this.graph.getGroupOnPos( event.canvasX, event.canvasY ); - if( group ) //on group - { - options.node = group; - menu_info = this.getGroupMenuOptions( group ); - } - else - menu_info = this.getCanvasMenuOptions(); - } - } - - //show menu - if(!menu_info) - return; - - var menu = new LiteGraph.ContextMenu( menu_info, options, ref_window ); - - function inner_option_clicked( v, options, e ) - { - if(!v) - return; - - if(v.content == "Remove Slot") - { - var info = v.slot; - if(info.input) - node.removeInput( info.slot ); - else if(info.output) - node.removeOutput( info.slot ); - return; - } - else if( v.content == "Rename Slot") - { - var info = v.slot; - var dialog = that.createDialog( "Name" , options ); - var input = dialog.querySelector("input"); - dialog.querySelector("button").addEventListener("click",function(e){ - if(input.value) - { - var slot_info = info.input ? node.getInputInfo( info.slot ) : node.getOutputInfo( info.slot ); - if( slot_info ) - slot_info.label = input.value; - that.setDirty(true); - } - dialog.close(); - }); - } - - //if(v.callback) - // return v.callback.call(that, node, options, e, menu, that, event ); - } -} - - - - - - -//API ************************************************* -//like rect but rounded corners -if(this.CanvasRenderingContext2D) -CanvasRenderingContext2D.prototype.roundRect = function (x, y, width, height, radius, radius_low) { - if ( radius === undefined ) { - radius = 5; - } - - if(radius_low === undefined) - radius_low = radius; - - this.moveTo(x + radius, y); - this.lineTo(x + width - radius, y); - this.quadraticCurveTo(x + width, y, x + width, y + radius); - - this.lineTo(x + width, y + height - radius_low); - this.quadraticCurveTo(x + width, y + height, x + width - radius_low, y + height); - this.lineTo(x + radius_low, y + height); - this.quadraticCurveTo(x, y + height, x, y + height - radius_low); - this.lineTo(x, y + radius); - this.quadraticCurveTo(x, y, x + radius, y); -} - -function compareObjects(a,b) -{ - for(var i in a) - if(a[i] != b[i]) - return false; - return true; -} -LiteGraph.compareObjects = compareObjects; - -function distance(a,b) -{ - return Math.sqrt( (b[0] - a[0]) * (b[0] - a[0]) + (b[1] - a[1]) * (b[1] - a[1]) ); -} -LiteGraph.distance = distance; - -function colorToString(c) -{ - return "rgba(" + Math.round(c[0] * 255).toFixed() + "," + Math.round(c[1] * 255).toFixed() + "," + Math.round(c[2] * 255).toFixed() + "," + (c.length == 4 ? c[3].toFixed(2) : "1.0") + ")"; -} -LiteGraph.colorToString = colorToString; - -function isInsideRectangle( x,y, left, top, width, height) -{ - if (left < x && (left + width) > x && - top < y && (top + height) > y) - return true; - return false; -} -LiteGraph.isInsideRectangle = isInsideRectangle; - -//[minx,miny,maxx,maxy] -function growBounding( bounding, x,y) -{ - if(x < bounding[0]) - bounding[0] = x; - else if(x > bounding[2]) - bounding[2] = x; - - if(y < bounding[1]) - bounding[1] = y; - else if(y > bounding[3]) - bounding[3] = y; -} -LiteGraph.growBounding = growBounding; - -//point inside boundin box -function isInsideBounding(p,bb) -{ - if (p[0] < bb[0][0] || - p[1] < bb[0][1] || - p[0] > bb[1][0] || - p[1] > bb[1][1]) - return false; - return true; -} -LiteGraph.isInsideBounding = isInsideBounding; - -//boundings overlap, format: [ startx, starty, width, height ] -function overlapBounding(a,b) -{ - var A_end_x = a[0] + a[2]; - var A_end_y = a[1] + a[3]; - var B_end_x = b[0] + b[2]; - var B_end_y = b[1] + b[3]; - - if ( a[0] > B_end_x || - a[1] > B_end_y || - A_end_x < b[0] || - A_end_y < b[1]) - return false; - return true; -} -LiteGraph.overlapBounding = overlapBounding; - -//Convert a hex value to its decimal value - the inputted hex must be in the -// format of a hex triplet - the kind we use for HTML colours. The function -// will return an array with three values. -function hex2num(hex) { - if(hex.charAt(0) == "#") hex = hex.slice(1); //Remove the '#' char - if there is one. - hex = hex.toUpperCase(); - var hex_alphabets = "0123456789ABCDEF"; - var value = new Array(3); - var k = 0; - var int1,int2; - for(var i=0;i<6;i+=2) { - int1 = hex_alphabets.indexOf(hex.charAt(i)); - int2 = hex_alphabets.indexOf(hex.charAt(i+1)); - value[k] = (int1 * 16) + int2; - k++; - } - return(value); -} - -LiteGraph.hex2num = hex2num; - -//Give a array with three values as the argument and the function will return -// the corresponding hex triplet. -function num2hex(triplet) { - var hex_alphabets = "0123456789ABCDEF"; - var hex = "#"; - var int1,int2; - for(var i=0;i<3;i++) { - int1 = triplet[i] / 16; - int2 = triplet[i] % 16; - - hex += hex_alphabets.charAt(int1) + hex_alphabets.charAt(int2); - } - return(hex); -} - -LiteGraph.num2hex = num2hex; - -/* LiteGraph GUI elements used for canvas editing *************************************/ - -/** -* ContextMenu from LiteGUI -* -* @class ContextMenu -* @constructor -* @param {Array} values (allows object { title: "Nice text", callback: function ... }) -* @param {Object} options [optional] Some options:\ -* - title: title to show on top of the menu -* - callback: function to call when an option is clicked, it receives the item information -* - ignore_item_callbacks: ignores the callback inside the item, it just calls the options.callback -* - event: you can pass a MouseEvent, this way the ContextMenu appears in that position -*/ -function ContextMenu( values, options ) -{ - options = options || {}; - this.options = options; - var that = this; - - //to link a menu with its parent - if(options.parentMenu) - { - if( options.parentMenu.constructor !== this.constructor ) - { - console.error("parentMenu must be of class ContextMenu, ignoring it"); - options.parentMenu = null; - } - else - { - this.parentMenu = options.parentMenu; - this.parentMenu.lock = true; - this.parentMenu.current_submenu = this; - } - } - - if(options.event && options.event.constructor !== MouseEvent && options.event.constructor !== CustomEvent) - { - console.error("Event passed to ContextMenu is not of type MouseEvent or CustomEvent. Ignoring it."); - options.event = null; - } - - var root = document.createElement("div"); - root.className = "litegraph litecontextmenu litemenubar-panel"; - root.style.minWidth = 100; - root.style.minHeight = 100; - root.style.pointerEvents = "none"; - setTimeout( function() { root.style.pointerEvents = "auto"; },100); //delay so the mouse up event is not caugh by this element - - //this prevents the default context browser menu to open in case this menu was created when pressing right button - root.addEventListener("mouseup", function(e){ - e.preventDefault(); return true; - }, true); - root.addEventListener("contextmenu", function(e) { - if(e.button != 2) //right button - return false; - e.preventDefault(); - return false; - },true); - - root.addEventListener("mousedown", function(e){ - if(e.button == 2) - { - that.close(); - e.preventDefault(); return true; - } - }, 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; - - //title - if(options.title) - { - var element = document.createElement("div"); - element.className = "litemenu-title"; - element.innerHTML = options.title; - root.appendChild(element); - } - - //entries - var num = 0; - for(var i in values) - { - var name = values.constructor == Array ? values[i] : i; - if( name != null && name.constructor !== String ) - name = name.content === undefined ? String(name) : name.content; - var value = values[i]; - this.addItem( name, value, options ); - num++; - } - - //close on leave - root.addEventListener("mouseleave", function(e) { - if(that.lock) - return; - that.close(e); - }); - - //insert before checking position - var root_document = document; - if(options.event) - root_document = options.event.target.ownerDocument; - - if(!root_document) - root_document = document; - root_document.body.appendChild(root); - - //compute best position - var left = options.left || 0; - var top = options.top || 0; - if(options.event) - { - left = (options.event.pageX - 10); - top = (options.event.pageY - 10); - if(options.title) - top -= 20; - - if(options.parentMenu) - { - var rect = options.parentMenu.root.getBoundingClientRect(); - left = rect.left + rect.width; - } - - var body_rect = document.body.getBoundingClientRect(); - var root_rect = root.getBoundingClientRect(); - - if(left > (body_rect.width - root_rect.width - 10)) - left = (body_rect.width - root_rect.width - 10); - if(top > (body_rect.height - root_rect.height - 10)) - top = (body_rect.height - root_rect.height - 10); - } - - root.style.left = left + "px"; - root.style.top = top + "px"; -} - -ContextMenu.prototype.addItem = function( name, value, options ) -{ - var that = this; - options = options || {}; - - var element = document.createElement("div"); - element.className = "litemenu-entry submenu"; - - var disabled = false; - - if(value === null) - { - element.classList.add("separator"); - //element.innerHTML = "
" - //continue; - } - else - { - element.innerHTML = value && value.title ? value.title : name; - element.value = value; - - if(value) - { - if(value.disabled) - { - disabled = true; - element.classList.add("disabled"); - } - if(value.submenu || value.has_submenu) - element.classList.add("has_submenu"); - } - - if(typeof(value) == "function") - { - element.dataset["value"] = name; - element.onclick_callback = value; - } - else - element.dataset["value"] = value; - - if(value.className) - element.className += " " + value.className; - } - - this.root.appendChild(element); - if(!disabled) - element.addEventListener("click", inner_onclick); - if(options.autoopen) - element.addEventListener("mouseenter", inner_over); - - function inner_over(e) - { - var value = this.value; - if(!value || !value.has_submenu) - return; - inner_onclick.call(this,e); - } - - //menu option clicked - function inner_onclick(e) { - var value = this.value; - var close_parent = true; - - if(that.current_submenu) - that.current_submenu.close(e); - - //global callback - if(options.callback) - { - var r = options.callback.call( this, value, options, e, that, options.node ); - if(r === true) - close_parent = false; - } - - //special cases - if(value) - { - if (value.callback && !options.ignore_item_callbacks && value.disabled !== true ) //item callback - { - var r = value.callback.call( this, value, options, e, that, options.node ); - if(r === true) - close_parent = false; - } - if(value.submenu) - { - if(!value.submenu.options) - throw("ContextMenu submenu needs options"); - var submenu = new that.constructor( value.submenu.options, { - callback: value.submenu.callback, - event: e, - parentMenu: that, - ignore_item_callbacks: value.submenu.ignore_item_callbacks, - title: value.submenu.title, - autoopen: options.autoopen - }); - close_parent = false; - } - } - - if(close_parent && !that.lock) - that.close(); - } - - return element; -} - -ContextMenu.prototype.close = function(e, ignore_parent_menu) -{ - if(this.root.parentNode) - this.root.parentNode.removeChild( this.root ); - if(this.parentMenu && !ignore_parent_menu) - { - this.parentMenu.lock = false; - this.parentMenu.current_submenu = null; - if( e === undefined ) - this.parentMenu.close(); - else if( e && !ContextMenu.isCursorOverElement( e, this.parentMenu.root) ) - { - ContextMenu.trigger( this.parentMenu.root, "mouseleave", e ); - } - } - if(this.current_submenu) - this.current_submenu.close(e, true); -} - -//this code is used to trigger events easily (used in the context menu mouseleave -ContextMenu.trigger = function(element, event_name, params, origin) -{ - var evt = document.createEvent( 'CustomEvent' ); - evt.initCustomEvent( event_name, true,true, params ); //canBubble, cancelable, detail - evt.srcElement = origin; - if( element.dispatchEvent ) - element.dispatchEvent( evt ); - else if( element.__events ) - element.__events.dispatchEvent( evt ); - //else nothing seems binded here so nothing to do - return evt; -} - -//returns the top most menu -ContextMenu.prototype.getTopMenu = function() -{ - if( this.options.parentMenu ) - return this.options.parentMenu.getTopMenu(); - return this; -} - -ContextMenu.prototype.getFirstEvent = function() -{ - if( this.options.parentMenu ) - return this.options.parentMenu.getFirstEvent(); - return this.options.event; -} - - - -ContextMenu.isCursorOverElement = function( event, element ) -{ - var left = event.pageX; - var top = event.pageY; - var rect = element.getBoundingClientRect(); - if(!rect) - return false; - if(top > rect.top && top < (rect.top + rect.height) && - left > rect.left && left < (rect.left + rect.width) ) - return true; - return false; -} - - - -LiteGraph.ContextMenu = ContextMenu; - -LiteGraph.closeAllContextMenus = function( ref_window ) -{ - ref_window = ref_window || window; - - var elements = ref_window.document.querySelectorAll(".litecontextmenu"); - if(!elements.length) - return; - - var result = []; - for(var i = 0; i < elements.length; i++) - result.push(elements[i]); - - for(var i in result) - { - if(result[i].close) - result[i].close(); - else if(result[i].parentNode) - result[i].parentNode.removeChild( result[i] ); - } -} - -LiteGraph.extendClass = function ( target, origin ) -{ - for(var i in origin) //copy class properties - { - if(target.hasOwnProperty(i)) - continue; - target[i] = origin[i]; - } - - if(origin.prototype) //copy prototype properties - for(var i in origin.prototype) //only enumerables - { - if(!origin.prototype.hasOwnProperty(i)) - continue; - - if(target.prototype.hasOwnProperty(i)) //avoid overwritting existing ones - continue; - - //copy getters - if(origin.prototype.__lookupGetter__(i)) - target.prototype.__defineGetter__(i, origin.prototype.__lookupGetter__(i)); - else - target.prototype[i] = origin.prototype[i]; - - //and setters - if(origin.prototype.__lookupSetter__(i)) - target.prototype.__defineSetter__(i, origin.prototype.__lookupSetter__(i)); - } -} - -//used to create nodes from wrapping functions -LiteGraph.getParameterNames = function(func) { - return (func + '') - .replace(/[/][/].*$/mg,'') // strip single-line comments - .replace(/\s+/g, '') // strip white space - .replace(/[/][*][^/*]*[*][/]/g, '') // strip multi-line comments /**/ - .split('){', 1)[0].replace(/^[^(]*[(]/, '') // extract the parameters - .replace(/=[^,]+/g, '') // strip any ES6 defaults - .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 || - window.mozRequestAnimationFrame || - (function( callback ){ - window.setTimeout(callback, 1000 / 60); - }); -} - -})(this); - -if(typeof(exports) != "undefined") - exports.LiteGraph = this.LiteGraph; - -//basic nodes -(function(global){ -var LiteGraph = global.LiteGraph; - -//Constant -function Time() -{ - this.addOutput("in ms","number"); - this.addOutput("in sec","number"); -} - -Time.title = "Time"; -Time.desc = "Time"; - -Time.prototype.onExecute = function() -{ - this.setOutputData(0, this.graph.globaltime * 1000 ); - this.setOutputData(1, this.graph.globaltime ); -} - -LiteGraph.registerNodeType("basic/time", Time); - - -//Subgraph: a node that contains a graph -function Subgraph() -{ - var that = this; - this.size = [120,80]; - - //create inner graph - this.subgraph = new LGraph(); - this.subgraph._subgraph_node = this; - this.subgraph._is_subgraph = true; - - this.subgraph.onGlobalInputAdded = this.onSubgraphNewGlobalInput.bind(this); - this.subgraph.onGlobalInputRenamed = this.onSubgraphRenamedGlobalInput.bind(this); - this.subgraph.onGlobalInputTypeChanged = this.onSubgraphTypeChangeGlobalInput.bind(this); - - this.subgraph.onGlobalOutputAdded = this.onSubgraphNewGlobalOutput.bind(this); - this.subgraph.onGlobalOutputRenamed = this.onSubgraphRenamedGlobalOutput.bind(this); - this.subgraph.onGlobalOutputTypeChanged = this.onSubgraphTypeChangeGlobalOutput.bind(this); - - this.addWidget("button","Open Graph",null,function( widget, graphcanvas ){ graphcanvas.openSubgraph(that.subgraph) }); - - this.bgcolor = "#353"; -} - -Subgraph.title = "Subgraph"; -Subgraph.desc = "Graph inside a node"; - -Subgraph.prototype.onSubgraphNewGlobalInput = function(name, type) -{ - //add input to the node - this.addInput(name, type); -} - -Subgraph.prototype.onSubgraphRenamedGlobalInput = function(oldname, name) -{ - var slot = this.findInputSlot( oldname ); - if(slot == -1) - return; - var info = this.getInputInfo(slot); - info.name = name; -} - -Subgraph.prototype.onSubgraphTypeChangeGlobalInput = function(name, type) -{ - var slot = this.findInputSlot( name ); - if(slot == -1) - return; - var info = this.getInputInfo(slot); - info.type = type; -} - - -Subgraph.prototype.onSubgraphNewGlobalOutput = function(name, type) -{ - //add output to the node - this.addOutput(name, type); -} - - -Subgraph.prototype.onSubgraphRenamedGlobalOutput = function(oldname, name) -{ - var slot = this.findOutputSlot( oldname ); - if(slot == -1) - return; - var info = this.getOutputInfo(slot); - info.name = name; -} - -Subgraph.prototype.onSubgraphTypeChangeGlobalOutput = function(name, type) -{ - var slot = this.findOutputSlot( name ); - if(slot == -1) - return; - var info = this.getOutputInfo(slot); - info.type = type; -} - - -Subgraph.prototype.getExtraMenuOptions = function(graphcanvas) -{ - var that = this; - return [ {content:"Open", callback: - function() { - graphcanvas.openSubgraph( that.subgraph ); - } - }]; -} - -Subgraph.prototype.onDrawForeground = function( ctx, graphcanvas ) -{ - /* - var node = this; - ctx.globalAlpha = 0.75; - graphcanvas.guiButton( ctx, [0,this.size[1] - 20, this.size[0], 19 ], "Open", function(){ graphcanvas.openSubgraph(node.subgraph); }); - ctx.globalAlpha = 1; - */ -} - -Subgraph.prototype.onResize = function(size) -{ - size[1] += 20; -} - -Subgraph.prototype.onExecute = function() -{ - //send inputs to subgraph global inputs - if(this.inputs) - for(var i = 0; i < this.inputs.length; i++) - { - var input = this.inputs[i]; - var value = this.getInputData(i); - this.subgraph.setGlobalInputData( input.name, value ); - } - - //execute - this.subgraph.runStep(); - - //send subgraph global outputs to outputs - if(this.outputs) - for(var i = 0; i < this.outputs.length; i++) - { - var output = this.outputs[i]; - var value = this.subgraph.getGlobalOutputData( output.name ); - this.setOutputData(i, value); - } -} - -Subgraph.prototype.configure = function(o) -{ - LGraphNode.prototype.configure.call(this, o); - //this.subgraph.configure(o.graph); -} - -Subgraph.prototype.serialize = function() -{ - var data = LGraphNode.prototype.serialize.call(this); - data.subgraph = this.subgraph.serialize(); - return data; -} - -Subgraph.prototype.clone = function() -{ - var node = LiteGraph.createNode(this.type); - var data = this.serialize(); - delete data["id"]; - delete data["inputs"]; - delete data["outputs"]; - node.configure(data); - return node; -} - - -LiteGraph.registerNodeType("graph/subgraph", Subgraph ); - - -//Input for a subgraph -function GlobalInput() -{ - - //random name to avoid problems with other outputs when added - var input_name = "input_" + (Math.random()*1000).toFixed(); - - this.addOutput(input_name, null ); - - this.properties = { name: input_name, type: null }; - - var that = this; - - Object.defineProperty( this.properties, "name", { - get: function() { - return input_name; - }, - set: function(v) { - if(v == "") - return; - - var info = that.getOutputInfo(0); - if(info.name == v) - return; - info.name = v; - if(that.graph) - that.graph.renameGlobalInput(input_name, v); - input_name = v; - }, - enumerable: true - }); - - Object.defineProperty( this.properties, "type", { - get: function() { return that.outputs[0].type; }, - set: function(v) { - that.outputs[0].type = v; - if(that.graph) - that.graph.changeGlobalInputType(input_name, that.outputs[0].type); - }, - enumerable: true - }); -} - -GlobalInput.title = "Input"; -GlobalInput.desc = "Input of the graph"; - -//When added to graph tell the graph this is a new global input -GlobalInput.prototype.onAdded = function() -{ - this.graph.addGlobalInput( this.properties.name, this.properties.type ); -} - -GlobalInput.prototype.onExecute = function() -{ - var name = this.properties.name; - - //read from global input - var data = this.graph.global_inputs[name]; - if(!data) return; - - //put through output - this.setOutputData(0,data.value); -} - -LiteGraph.registerNodeType("graph/input", GlobalInput); - - - -//Output for a subgraph -function GlobalOutput() -{ - //random name to avoid problems with other outputs when added - var output_name = "output_" + (Math.random()*1000).toFixed(); - - this.addInput(output_name, null); - - this._value = null; - - this.properties = {name: output_name, type: null }; - - var that = this; - - Object.defineProperty(this.properties, "name", { - get: function() { - return output_name; - }, - set: function(v) { - if(v == "") - return; - - var info = that.getInputInfo(0); - if(info.name == v) - return; - info.name = v; - if(that.graph) - that.graph.renameGlobalOutput(output_name, v); - output_name = v; - }, - enumerable: true - }); - - Object.defineProperty(this.properties, "type", { - get: function() { return that.inputs[0].type; }, - set: function(v) { - that.inputs[0].type = v; - if(that.graph) - that.graph.changeGlobalInputType( output_name, that.inputs[0].type ); - }, - enumerable: true - }); -} - -GlobalOutput.title = "Output"; -GlobalOutput.desc = "Output of the graph"; - -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._value = this.getInputData(0); - this.graph.setGlobalOutputData( this.properties.name, this._value ); -} - -LiteGraph.registerNodeType("graph/output", GlobalOutput); - - - -//Constant -function Constant() -{ - this.addOutput("value","number"); - this.addProperty( "value", 1.0 ); - this.editable = { property:"value", type:"number" }; -} - -Constant.title = "Const"; -Constant.desc = "Constant value"; - - -Constant.prototype.setValue = function(v) -{ - if( typeof(v) == "string") v = parseFloat(v); - this.properties["value"] = v; - this.setDirtyCanvas(true); -}; - -Constant.prototype.onExecute = function() -{ - this.setOutputData(0, parseFloat( this.properties["value"] ) ); -} - -Constant.prototype.onDrawBackground = function(ctx) -{ - //show the current value - this.outputs[0].label = this.properties["value"].toFixed(3); -} - -Constant.prototype.onWidget = function(e,widget) -{ - if(widget.name == "value") - this.setValue(widget.value); -} - -LiteGraph.registerNodeType("basic/const", Constant); - - -//Watch a value in the editor -function Watch() -{ - this.size = [60,20]; - this.addInput("value",0,{label:""}); - this.addOutput("value",0,{label:""}); - this.addProperty( "value", "" ); -} - -Watch.title = "Watch"; -Watch.desc = "Show value of input"; - -Watch.prototype.onExecute = function() -{ - this.properties.value = this.getInputData(0); - this.setOutputData(0, this.properties.value); -} - -Watch.prototype.onDrawBackground = function(ctx) -{ - //show the current value - if(this.inputs[0] && this.properties["value"] != null) - { - if (this.properties["value"].constructor === Number ) - this.inputs[0].label = this.properties["value"].toFixed(3); - else - this.inputs[0].label = String(this.properties.value); - } -} - -LiteGraph.registerNodeType("basic/watch", Watch); - -//Watch a value in the editor -function Pass() -{ - this.addInput("in",0); - this.addOutput("out",0); - this.size = [40,20]; -} - -Pass.title = "Pass"; -Pass.desc = "Allows to connect different types"; - -Pass.prototype.onExecute = function() -{ - this.setOutputData( 0, this.getInputData(0) ); -} - -LiteGraph.registerNodeType("basic/pass", Pass); - - -//Show value inside the debug console -function Console() -{ - this.mode = LiteGraph.ON_EVENT; - this.size = [60,20]; - this.addProperty( "msg", "" ); - this.addInput("log", LiteGraph.EVENT); - this.addInput("msg",0); -} - -Console.title = "Console"; -Console.desc = "Show value inside the console"; - -Console.prototype.onAction = function(action, param) -{ - if(action == "log") - console.log( param ); - else if(action == "warn") - console.warn( param ); - else if(action == "error") - console.error( param ); -} - -Console.prototype.onExecute = function() -{ - var msg = this.getInputData(1); - if(msg !== null) - this.properties.msg = msg; - console.log(msg); -} - -Console.prototype.onGetInputs = function() -{ - return [["log",LiteGraph.ACTION],["warn",LiteGraph.ACTION],["error",LiteGraph.ACTION]]; -} - -LiteGraph.registerNodeType("basic/console", Console ); - - - -//Show value inside the debug console -function NodeScript() -{ - this.size = [60,20]; - this.addProperty( "onExecute", "" ); - this.addInput("in", ""); - this.addInput("in2", ""); - this.addOutput("out", ""); - this.addOutput("out2", ""); - - this._func = null; -} - -NodeScript.title = "Script"; -NodeScript.desc = "executes a code"; - -NodeScript.widgets_info = { - "onExecute": { type:"code" } -}; - -NodeScript.prototype.onPropertyChanged = function(name,value) -{ - if(name == "onExecute" && LiteGraph.allow_scripts ) - { - this._func = null; - try - { - this._func = new Function( value ); - } - catch (err) - { - console.error("Error parsing script"); - console.error(err); - } - } -} - -NodeScript.prototype.onExecute = function() -{ - if(!this._func) - return; - - try - { - this._func.call(this); - } - catch (err) - { - console.error("Error in script"); - console.error(err); - } -} - -LiteGraph.registerNodeType("basic/script", NodeScript ); - - -})(this); -//event related nodes -(function(global){ -var LiteGraph = global.LiteGraph; - -//Show value inside the debug console -function LogEvent() -{ - this.size = [60,20]; - this.addInput("event", LiteGraph.ACTION); -} - -LogEvent.title = "Log Event"; -LogEvent.desc = "Log event in console"; - -LogEvent.prototype.onAction = function( action, param ) -{ - console.log( action, param ); -} - -LiteGraph.registerNodeType("events/log", LogEvent ); - - -//Filter events -function FilterEvent() -{ - this.size = [60,20]; - this.addInput("event", LiteGraph.ACTION); - this.addOutput("event", LiteGraph.EVENT); - this.properties = { - equal_to: "", - has_property:"", - property_equal_to: "" - }; -} - -FilterEvent.title = "Filter Event"; -FilterEvent.desc = "Blocks events that do not match the filter"; - -FilterEvent.prototype.onAction = function( action, param ) -{ - if( param == null ) - return; - - if( this.properties.equal_to && this.properties.equal_to != param ) - return; - - if( this.properties.has_property ) - { - var prop = param[ this.properties.has_property ]; - if( prop == null ) - return; - - if( this.properties.property_equal_to && this.properties.property_equal_to != prop ) - return; - } - - this.triggerSlot(0,param); -} - -LiteGraph.registerNodeType("events/filter", FilterEvent ); - -/* -//Filter events -function SetModeNode() -{ - this.size = [60,20]; - this.addInput("event", LiteGraph.ACTION); - this.addOutput("event", LiteGraph.EVENT); - this.properties = { - equal_to: "", - has_property:"", - property_equal_to: "" - }; -} - -SetModeNode.title = "Set Node Mode"; -SetModeNode.desc = "Changes a node mode"; - -SetModeNode.prototype.onAction = function( action, param ) -{ - if( param == null ) - return; - - if( this.properties.equal_to && this.properties.equal_to != param ) - return; - - if( this.properties.has_property ) - { - var prop = param[ this.properties.has_property ]; - if( prop == null ) - return; - - if( this.properties.property_equal_to && this.properties.property_equal_to != prop ) - return; - } - - this.triggerSlot(0,param); -} - -LiteGraph.registerNodeType("events/set_mode", SetModeNode ); -*/ - -//Show value inside the debug console -function DelayEvent() -{ - this.size = [60,20]; - this.addProperty( "time", 1000 ); - this.addInput("event", LiteGraph.ACTION); - this.addOutput("on_time", LiteGraph.EVENT); - - this._pending = []; -} - -DelayEvent.title = "Delay"; -DelayEvent.desc = "Delays one event"; - -DelayEvent.prototype.onAction = function(action, param) -{ - this._pending.push([ this.properties.time, param ]); -} - -DelayEvent.prototype.onExecute = function() -{ - var dt = this.graph.elapsed_time * 1000; //in ms - - for(var i = 0; i < this._pending.length; ++i) - { - var action = this._pending[i]; - action[0] -= dt; - if( action[0] > 0 ) - continue; - - //remove - this._pending.splice(i,1); - --i; - - //trigger - this.trigger(null, action[1]); - } -} - -DelayEvent.prototype.onGetInputs = function() -{ - return [["event",LiteGraph.ACTION]]; -} - -LiteGraph.registerNodeType("events/delay", DelayEvent ); - - -})(this); -//widgets -(function(global){ -var LiteGraph = global.LiteGraph; - - /* Button ****************/ - - function WidgetButton() - { - this.addOutput( "clicked", LiteGraph.EVENT ); - this.addProperty( "text","" ); - this.addProperty( "font_size", 40 ); - this.addProperty( "message", "" ); - this.size = [64,84]; - } - - WidgetButton.title = "Button"; - WidgetButton.desc = "Triggers an event"; - - WidgetButton.font = "Arial"; - WidgetButton.prototype.onDrawForeground = function(ctx) - { - if(this.flags.collapsed) - return; - - //ctx.font = "40px Arial"; - //ctx.textAlign = "center"; - ctx.fillStyle = "black"; - ctx.fillRect(1,1,this.size[0] - 3, this.size[1] - 3); - ctx.fillStyle = "#AAF"; - ctx.fillRect(0,0,this.size[0] - 3, this.size[1] - 3); - ctx.fillStyle = this.clicked ? "white" : (this.mouseOver ? "#668" : "#334"); - ctx.fillRect(1,1,this.size[0] - 4, this.size[1] - 4); - - if( this.properties.text || this.properties.text === 0 ) - { - var font_size = this.properties.font_size || 30; - ctx.textAlign = "center"; - ctx.fillStyle = this.clicked ? "black" : "white"; - ctx.font = font_size + "px " + WidgetButton.font; - ctx.fillText( this.properties.text, this.size[0] * 0.5, this.size[1] * 0.5 + font_size * 0.3 ); - ctx.textAlign = "left"; - } - } - - WidgetButton.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.clicked = true; - this.trigger( "clicked", this.properties.message ); - return true; - } - } - - WidgetButton.prototype.onMouseUp = function(e) - { - this.clicked = false; - } - - - LiteGraph.registerNodeType("widget/button", WidgetButton ); - - - function WidgetToggle() - { - this.addInput( "", "boolean" ); - this.addInput( "e", LiteGraph.ACTION ); - 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.onAction = function(action) - { - this.properties.value = !this.properties.value; - this.trigger( "e", this.properties.value ); - } - - 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.graph._version++; - this.trigger( "e", this.properties.value ); - return true; - } - } - - LiteGraph.registerNodeType("widget/toggle", WidgetToggle ); - - /* Number ****************/ - - function WidgetNumber() - { - this.addOutput("",'number'); - this.size = [74,54]; - this.properties = {min:-1000,max:1000,value:1,step:1}; - this.old_y = -1; - this._remainder = 0; - this._precision = 0; - this.mouse_captured = false; - } - - WidgetNumber.title = "Number"; - WidgetNumber.desc = "Widget to select number value"; - - WidgetNumber.pixels_threshold = 10; - WidgetNumber.markers_color = "#666"; - - WidgetNumber.prototype.onDrawForeground = function(ctx) - { - var x = this.size[0]*0.5; - var h = this.size[1]; - if(h > 30) - { - ctx.fillStyle = WidgetNumber.markers_color; - ctx.beginPath(); ctx.moveTo(x,h*0.1); ctx.lineTo(x+h*0.1,h*0.2); ctx.lineTo(x+h*-0.1,h*0.2); ctx.fill(); - ctx.beginPath(); ctx.moveTo(x,h*0.9); ctx.lineTo(x+h*0.1,h*0.8); ctx.lineTo(x+h*-0.1,h*0.8); ctx.fill(); - ctx.font = (h * 0.7).toFixed(1) + "px Arial"; - } - else - ctx.font = (h * 0.8).toFixed(1) + "px Arial"; - - ctx.textAlign = "center"; - ctx.font = (h * 0.7).toFixed(1) + "px Arial"; - ctx.fillStyle = "#EEE"; - ctx.fillText( this.properties.value.toFixed( this._precision ), x, h * 0.75 ); - } - - WidgetNumber.prototype.onExecute = function() - { - this.setOutputData(0, this.properties.value ); - } - - WidgetNumber.prototype.onPropertyChanged = function(name,value) - { - var t = (this.properties.step + "").split("."); - this._precision = t.length > 1 ? t[1].length : 0; - } - - WidgetNumber.prototype.onMouseDown = function(e, pos) - { - if(pos[1] < 0) - return; - - this.old_y = e.canvasY; - this.captureInput(true); - this.mouse_captured = true; - - return true; - } - - WidgetNumber.prototype.onMouseMove = function(e) - { - if(!this.mouse_captured) - return; - - var delta = this.old_y - e.canvasY; - if(e.shiftKey) - delta *= 10; - if(e.metaKey || e.altKey) - delta *= 0.1; - this.old_y = e.canvasY; - - var steps = (this._remainder + delta / WidgetNumber.pixels_threshold); - this._remainder = steps % 1; - steps = steps|0; - - var v = Math.clamp( this.properties.value + steps * this.properties.step, this.properties.min, this.properties.max ); - this.properties.value = v; - this.graph._version++; - this.setDirtyCanvas(true); - } - - WidgetNumber.prototype.onMouseUp = function(e,pos) - { - if(e.click_time < 200) - { - var steps = pos[1] > this.size[1] * 0.5 ? -1 : 1; - this.properties.value = Math.clamp( this.properties.value + steps * this.properties.step, this.properties.min, this.properties.max ); - this.graph._version++; - this.setDirtyCanvas(true); - } - - if( this.mouse_captured ) - { - this.mouse_captured = false; - this.captureInput(false); - } - } - - LiteGraph.registerNodeType("widget/number", WidgetNumber ); - - - /* Knob ****************/ - - function WidgetKnob() - { - this.addOutput("",'number'); - this.size = [64,84]; - this.properties = {min:0,max:1,value:0.5,wcolor:"#7AF",size:50}; - } - - WidgetKnob.title = "Knob"; - WidgetKnob.desc = "Circular controller"; - WidgetKnob.widgets = [{name:"increase",text:"+",type:"minibutton"},{name:"decrease",text:"-",type:"minibutton"}]; - - - WidgetKnob.prototype.onAdded = function() - { - this.value = (this.properties["value"] - this.properties["min"]) / (this.properties["max"] - this.properties["min"]); - - this.imgbg = this.loadImage("imgs/knob_bg.png"); - this.imgfg = this.loadImage("imgs/knob_fg.png"); - } - - WidgetKnob.prototype.onDrawImageKnob = function(ctx) - { - if(!this.imgfg || !this.imgfg.width) return; - - var d = this.imgbg.width*0.5; - var scale = this.size[0] / this.imgfg.width; - - ctx.save(); - ctx.translate(0,20); - ctx.scale(scale,scale); - ctx.drawImage(this.imgbg,0,0); - //ctx.drawImage(this.imgfg,0,20); - - ctx.translate(d,d); - ctx.rotate(this.value * (Math.PI*2) * 6/8 + Math.PI * 10/8); - //ctx.rotate(this.value * (Math.PI*2)); - ctx.translate(-d,-d); - ctx.drawImage(this.imgfg,0,0); - - ctx.restore(); - - if(this.title) - { - ctx.font = "bold 16px Criticized,Tahoma"; - ctx.fillStyle="rgba(100,100,100,0.8)"; - ctx.textAlign = "center"; - ctx.fillText(this.title.toUpperCase(), this.size[0] * 0.5, 18 ); - ctx.textAlign = "left"; - } - } - - WidgetKnob.prototype.onDrawVectorKnob = function(ctx) - { - if(!this.imgfg || !this.imgfg.width) return; - - //circle around - ctx.lineWidth = 1; - ctx.strokeStyle= this.mouseOver ? "#FFF" : "#AAA"; - ctx.fillStyle="#000"; - ctx.beginPath(); - ctx.arc(this.size[0] * 0.5,this.size[1] * 0.5 + 10,this.properties.size * 0.5,0,Math.PI*2,true); - ctx.stroke(); - - if(this.value > 0) - { - ctx.strokeStyle=this.properties["wcolor"]; - ctx.lineWidth = (this.properties.size * 0.2); - ctx.beginPath(); - ctx.arc(this.size[0] * 0.5,this.size[1] * 0.5 + 10,this.properties.size * 0.35,Math.PI * -0.5 + Math.PI*2 * this.value,Math.PI * -0.5,true); - ctx.stroke(); - ctx.lineWidth = 1; - } - - ctx.font = (this.properties.size * 0.2) + "px Arial"; - ctx.fillStyle="#AAA"; - ctx.textAlign = "center"; - - var str = this.properties["value"]; - if(typeof(str) == 'number') - str = str.toFixed(2); - - ctx.fillText(str,this.size[0] * 0.5,this.size[1]*0.65); - ctx.textAlign = "left"; - } - - WidgetKnob.prototype.onDrawForeground = function(ctx) - { - this.onDrawImageKnob(ctx); - } - - WidgetKnob.prototype.onExecute = function() - { - this.setOutputData(0, this.properties["value"] ); - - this.boxcolor = LiteGraph.colorToString([this.value,this.value,this.value]); - } - - WidgetKnob.prototype.onMouseDown = function(e) - { - if(!this.imgfg || !this.imgfg.width) return; - - //this.center = [this.imgbg.width * 0.5, this.imgbg.height * 0.5 + 20]; - //this.radius = this.imgbg.width * 0.5; - this.center = [this.size[0] * 0.5, this.size[1] * 0.5 + 20]; - this.radius = this.size[0] * 0.5; - - if(e.canvasY - this.pos[1] < 20 || LiteGraph.distance([e.canvasX,e.canvasY],[this.pos[0] + this.center[0],this.pos[1] + this.center[1]]) > this.radius) - return false; - - this.oldmouse = [ e.canvasX - this.pos[0], e.canvasY - this.pos[1] ]; - this.captureInput(true); - - /* - var tmp = this.localToScreenSpace(0,0); - this.trace(tmp[0] + "," + tmp[1]); */ - return true; - } - - WidgetKnob.prototype.onMouseMove = function(e) - { - if(!this.oldmouse) return; - - var m = [ e.canvasX - this.pos[0], e.canvasY - this.pos[1] ]; - - var v = this.value; - v -= (m[1] - this.oldmouse[1]) * 0.01; - if(v > 1.0) v = 1.0; - else if(v < 0.0) v = 0.0; - - this.value = v; - this.properties["value"] = this.properties["min"] + (this.properties["max"] - this.properties["min"]) * this.value; - - this.oldmouse = m; - this.setDirtyCanvas(true); - } - - WidgetKnob.prototype.onMouseUp = function(e) - { - if(this.oldmouse) - { - this.oldmouse = null; - this.captureInput(false); - } - } - - WidgetKnob.prototype.onMouseLeave = function(e) - { - //this.oldmouse = null; - } - - WidgetKnob.prototype.onWidget = function(e,widget) - { - if(widget.name=="increase") - this.onPropertyChanged("size", this.properties.size + 10); - else if(widget.name=="decrease") - this.onPropertyChanged("size", this.properties.size - 10); - } - - WidgetKnob.prototype.onPropertyChanged = function(name,value) - { - if(name=="wcolor") - this.properties[name] = value; - else if(name=="size") - { - value = parseInt(value); - this.properties[name] = value; - this.size = [value+4,value+24]; - this.setDirtyCanvas(true,true); - } - else if(name=="min" || name=="max" || name=="value") - { - this.properties[name] = parseFloat(value); - } - else - return false; - return true; - } - - LiteGraph.registerNodeType("widget/knob", WidgetKnob); - - //Show value inside the debug console - function WidgetSliderGUI() - { - this.addOutput("","number"); - this.properties = { - value: 0.5, - min: 0, - max: 1, - text: "V" - }; - var that = this; - this.size = [80,60]; - this.slider = this.addWidget("slider","V", this.properties.value, function(v){ that.properties.value = v; }, this.properties ); - } - - WidgetSliderGUI.title = "Internal Slider"; - - WidgetSliderGUI.prototype.onPropertyChanged = function(name,value) - { - if(name == "value") - this.slider.value = value; - } - - WidgetSliderGUI.prototype.onExecute = function() - { - this.setOutputData(0,this.properties.value); - } - - - LiteGraph.registerNodeType("widget/internal_slider", WidgetSliderGUI ); - - //Widget H SLIDER - function WidgetHSlider() - { - this.size = [160,26]; - this.addOutput("",'number'); - this.properties = {wcolor:"#7AF",min:0,max:1,value:0.5}; - } - - WidgetHSlider.title = "H.Slider"; - WidgetHSlider.desc = "Linear slider controller"; - - WidgetHSlider.prototype.onAdded = function() - { - this.value = 0.5; - this.imgfg = this.loadImage("imgs/slider_fg.png"); - } - - WidgetHSlider.prototype.onDrawVectorial = function(ctx) - { - if(!this.imgfg || !this.imgfg.width) return; - - //border - ctx.lineWidth = 1; - ctx.strokeStyle= this.mouseOver ? "#FFF" : "#AAA"; - ctx.fillStyle="#000"; - ctx.beginPath(); - ctx.rect(2,0,this.size[0]-4,20); - ctx.stroke(); - - ctx.fillStyle=this.properties["wcolor"]; - ctx.beginPath(); - ctx.rect(2+(this.size[0]-4-20)*this.value,0, 20,20); - ctx.fill(); - } - - WidgetHSlider.prototype.onDrawImage = function(ctx) - { - if(!this.imgfg || !this.imgfg.width) - return; - - //border - ctx.lineWidth = 1; - ctx.fillStyle="#000"; - ctx.fillRect(2,9,this.size[0]-4,2); - - ctx.strokeStyle= "#333"; - ctx.beginPath(); - ctx.moveTo(2,9); - ctx.lineTo(this.size[0]-4,9); - ctx.stroke(); - - ctx.strokeStyle= "#AAA"; - ctx.beginPath(); - ctx.moveTo(2,11); - ctx.lineTo(this.size[0]-4,11); - ctx.stroke(); - - ctx.drawImage(this.imgfg, 2+(this.size[0]-4)*this.value - this.imgfg.width*0.5,-this.imgfg.height*0.5 + 10); - }, - - WidgetHSlider.prototype.onDrawForeground = function(ctx) - { - this.onDrawImage(ctx); - } - - WidgetHSlider.prototype.onExecute = function() - { - this.properties["value"] = this.properties["min"] + (this.properties["max"] - this.properties["min"]) * this.value; - this.setOutputData(0, this.properties["value"] ); - this.boxcolor = LiteGraph.colorToString([this.value,this.value,this.value]); - } - - WidgetHSlider.prototype.onMouseDown = function(e) - { - if(e.canvasY - this.pos[1] < 0) - return false; - - this.oldmouse = [ e.canvasX - this.pos[0], e.canvasY - this.pos[1] ]; - this.captureInput(true); - return true; - } - - WidgetHSlider.prototype.onMouseMove = function(e) - { - if(!this.oldmouse) return; - - var m = [ e.canvasX - this.pos[0], e.canvasY - this.pos[1] ]; - - var v = this.value; - var delta = (m[0] - this.oldmouse[0]); - v += delta / this.size[0]; - if(v > 1.0) v = 1.0; - else if(v < 0.0) v = 0.0; - - this.value = v; - - this.oldmouse = m; - this.setDirtyCanvas(true); - } - - WidgetHSlider.prototype.onMouseUp = function(e) - { - this.oldmouse = null; - this.captureInput(false); - } - - WidgetHSlider.prototype.onMouseLeave = function(e) - { - //this.oldmouse = null; - } - - WidgetHSlider.prototype.onPropertyChanged = function(name,value) - { - if(name=="wcolor") - this.properties[name] = value; - else - return false; - return true; - } - - LiteGraph.registerNodeType("widget/hslider", WidgetHSlider ); - - - function WidgetProgress() - { - this.size = [160,26]; - this.addInput("",'number'); - this.properties = {min:0,max:1,value:0,wcolor:"#AAF"}; - } - - WidgetProgress.title = "Progress"; - WidgetProgress.desc = "Shows data in linear progress"; - - WidgetProgress.prototype.onExecute = function() - { - var v = this.getInputData(0); - if( v != undefined ) - this.properties["value"] = v; - } - - WidgetProgress.prototype.onDrawForeground = function(ctx) - { - //border - ctx.lineWidth = 1; - ctx.fillStyle=this.properties.wcolor; - var v = (this.properties.value - this.properties.min) / (this.properties.max - this.properties.min); - v = Math.min(1,v); - v = Math.max(0,v); - ctx.fillRect(2,2,(this.size[0]-4)*v,this.size[1]-4); - } - - LiteGraph.registerNodeType("widget/progress", WidgetProgress); - - - /* - LiteGraph.registerNodeType("widget/kpad",{ - title: "KPad", - desc: "bidimensional slider", - size: [200,200], - outputs: [["x",'number'],["y",'number']], - properties:{x:0,y:0,borderColor:"#333",bgcolorTop:"#444",bgcolorBottom:"#000",shadowSize:1, borderRadius:2}, - - createGradient: function(ctx) - { - this.lineargradient = ctx.createLinearGradient(0,0,0,this.size[1]); - this.lineargradient.addColorStop(0,this.properties["bgcolorTop"]); - this.lineargradient.addColorStop(1,this.properties["bgcolorBottom"]); - }, - - onDrawBackground: function(ctx) - { - if(!this.lineargradient) - this.createGradient(ctx); - - ctx.lineWidth = 1; - ctx.strokeStyle = this.properties["borderColor"]; - //ctx.fillStyle = "#ebebeb"; - ctx.fillStyle = this.lineargradient; - - ctx.shadowColor = "#000"; - ctx.shadowOffsetX = 0; - ctx.shadowOffsetY = 0; - ctx.shadowBlur = this.properties["shadowSize"]; - ctx.roundRect(0,0,this.size[0],this.size[1],this.properties["shadowSize"]); - ctx.fill(); - ctx.shadowColor = "rgba(0,0,0,0)"; - ctx.stroke(); - - ctx.fillStyle = "#A00"; - ctx.fillRect(this.size[0] * this.properties["x"] - 5, this.size[1] * this.properties["y"] - 5,10,10); - }, - - onWidget: function(e,widget) - { - if(widget.name == "update") - { - this.lineargradient = null; - this.setDirtyCanvas(true); - } - }, - - onExecute: function() - { - this.setOutputData(0, this.properties["x"] ); - this.setOutputData(1, this.properties["y"] ); - }, - - onMouseDown: function(e) - { - if(e.canvasY - this.pos[1] < 0) - return false; - - this.oldmouse = [ e.canvasX - this.pos[0], e.canvasY - this.pos[1] ]; - this.captureInput(true); - return true; - }, - - onMouseMove: function(e) - { - if(!this.oldmouse) return; - - var m = [ e.canvasX - this.pos[0], e.canvasY - this.pos[1] ]; - - this.properties.x = m[0] / this.size[0]; - this.properties.y = m[1] / this.size[1]; - - if(this.properties.x > 1.0) this.properties.x = 1.0; - else if(this.properties.x < 0.0) this.properties.x = 0.0; - - if(this.properties.y > 1.0) this.properties.y = 1.0; - else if(this.properties.y < 0.0) this.properties.y = 0.0; - - this.oldmouse = m; - this.setDirtyCanvas(true); - }, - - onMouseUp: function(e) - { - if(this.oldmouse) - { - this.oldmouse = null; - this.captureInput(false); - } - }, - - onMouseLeave: function(e) - { - //this.oldmouse = null; - } - }); - - - - LiteGraph.registerNodeType("widget/button", { - title: "Button", - desc: "A send command button", - - widgets: [{name:"test",text:"Test Button",type:"button"}], - size: [100,40], - properties:{text:"clickme",command:"",color:"#7AF",bgcolorTop:"#f0f0f0",bgcolorBottom:"#e0e0e0",fontsize:"16"}, - outputs:[["M","module"]], - - createGradient: function(ctx) - { - this.lineargradient = ctx.createLinearGradient(0,0,0,this.size[1]); - this.lineargradient.addColorStop(0,this.properties["bgcolorTop"]); - this.lineargradient.addColorStop(1,this.properties["bgcolorBottom"]); - }, - - drawVectorShape: function(ctx) - { - ctx.fillStyle = this.mouseOver ? this.properties["color"] : "#AAA"; - - if(this.clicking) - ctx.fillStyle = "#FFF"; - - ctx.strokeStyle = "#AAA"; - ctx.roundRect(5,5,this.size[0] - 10,this.size[1] - 10,4); - ctx.stroke(); - - if(this.mouseOver) - ctx.fill(); - - //ctx.fillRect(5,20,this.size[0] - 10,this.size[1] - 30); - - ctx.fillStyle = this.mouseOver ? "#000" : "#AAA"; - ctx.font = "bold " + this.properties["fontsize"] + "px Criticized,Tahoma"; - ctx.textAlign = "center"; - ctx.fillText(this.properties["text"],this.size[0]*0.5,this.size[1]*0.5 + 0.5*parseInt(this.properties["fontsize"])); - ctx.textAlign = "left"; - }, - - drawBevelShape: function(ctx) - { - ctx.shadowColor = "#000"; - ctx.shadowOffsetX = 0; - ctx.shadowOffsetY = 0; - ctx.shadowBlur = this.properties["shadowSize"]; - - if(!this.lineargradient) - this.createGradient(ctx); - - ctx.fillStyle = this.mouseOver ? this.properties["color"] : this.lineargradient; - if(this.clicking) - ctx.fillStyle = "#444"; - - ctx.strokeStyle = "#FFF"; - ctx.roundRect(5,5,this.size[0] - 10,this.size[1] - 10,4); - ctx.fill(); - ctx.shadowColor = "rgba(0,0,0,0)"; - ctx.stroke(); - - ctx.fillStyle = this.mouseOver ? "#000" : "#444"; - ctx.font = "bold " + this.properties["fontsize"] + "px Century Gothic"; - ctx.textAlign = "center"; - ctx.fillText(this.properties["text"],this.size[0]*0.5,this.size[1]*0.5 + 0.40*parseInt(this.properties["fontsize"])); - ctx.textAlign = "left"; - }, - - onDrawForeground: function(ctx) - { - this.drawBevelShape(ctx); - }, - - clickButton: function() - { - var module = this.getOutputModule(0); - if(this.properties["command"] && this.properties["command"] != "") - { - if (! module.executeAction(this.properties["command"]) ) - this.trace("Error executing action in other module"); - } - else if(module && module.onTrigger) - { - module.onTrigger(); - } - }, - - onMouseDown: function(e) - { - if(e.canvasY - this.pos[1] < 2) - return false; - this.clickButton(); - this.clicking = true; - return true; - }, - - onMouseUp: function(e) - { - this.clicking = false; - }, - - onExecute: function() - { - }, - - onWidget: function(e,widget) - { - if(widget.name == "test") - { - this.clickButton(); - } - }, - - onPropertyChanged: function(name,value) - { - this.properties[name] = value; - return true; - } - }); - */ - - - function WidgetText() - { - this.addInputs("",0); - this.properties = { value:"...",font:"Arial", fontsize:18, color:"#AAA", align:"left", glowSize:0, decimals:1 }; - } - - WidgetText.title = "Text"; - WidgetText.desc = "Shows the input value"; - WidgetText.widgets = [{name:"resize",text:"Resize box",type:"button"},{name:"led_text",text:"LED",type:"minibutton"},{name:"normal_text",text:"Normal",type:"minibutton"}]; - - WidgetText.prototype.onDrawForeground = function(ctx) - { - //ctx.fillStyle="#000"; - //ctx.fillRect(0,0,100,60); - ctx.fillStyle = this.properties["color"]; - var v = this.properties["value"]; - - if(this.properties["glowSize"]) - { - ctx.shadowColor = this.properties["color"]; - ctx.shadowOffsetX = 0; - ctx.shadowOffsetY = 0; - ctx.shadowBlur = this.properties["glowSize"]; - } - else - ctx.shadowColor = "transparent"; - - var fontsize = this.properties["fontsize"]; - - ctx.textAlign = this.properties["align"]; - ctx.font = fontsize.toString() + "px " + this.properties["font"]; - this.str = typeof(v) == 'number' ? v.toFixed(this.properties["decimals"]) : v; - - if( typeof(this.str) == 'string') - { - var lines = this.str.split("\\n"); - for(var i in lines) - ctx.fillText(lines[i],this.properties["align"] == "left" ? 15 : this.size[0] - 15, fontsize * -0.15 + fontsize * (parseInt(i)+1) ); - } - - ctx.shadowColor = "transparent"; - this.last_ctx = ctx; - ctx.textAlign = "left"; - } - - WidgetText.prototype.onExecute = function() - { - var v = this.getInputData(0); - if(v != null) - this.properties["value"] = v; - //this.setDirtyCanvas(true); - } - - WidgetText.prototype.resize = function() - { - if(!this.last_ctx) return; - - var lines = this.str.split("\\n"); - this.last_ctx.font = this.properties["fontsize"] + "px " + this.properties["font"]; - var max = 0; - for(var i in lines) - { - var w = this.last_ctx.measureText(lines[i]).width; - if(max < w) max = w; - } - this.size[0] = max + 20; - this.size[1] = 4 + lines.length * this.properties["fontsize"]; - - this.setDirtyCanvas(true); - } - - WidgetText.prototype.onWidget = function(e,widget) - { - if(widget.name == "resize") - this.resize(); - else if (widget.name == "led_text") - { - this.properties["font"] = "Digital"; - this.properties["glowSize"] = 4; - this.setDirtyCanvas(true); - } - else if (widget.name == "normal_text") - { - this.properties["font"] = "Arial"; - this.setDirtyCanvas(true); - } - } - - WidgetText.prototype.onPropertyChanged = function(name,value) - { - this.properties[name] = value; - this.str = typeof(value) == 'number' ? value.toFixed(3) : value; - //this.resize(); - return true; - } - - LiteGraph.registerNodeType("widget/text", WidgetText ); - - - function WidgetPanel() - { - this.size = [200,100]; - this.properties = {borderColor:"#ffffff",bgcolorTop:"#f0f0f0",bgcolorBottom:"#e0e0e0",shadowSize:2, borderRadius:3}; - } - - WidgetPanel.title = "Panel"; - WidgetPanel.desc = "Non interactive panel"; - WidgetPanel.widgets = [{name:"update",text:"Update",type:"button"}]; - - - WidgetPanel.prototype.createGradient = function(ctx) - { - if(this.properties["bgcolorTop"] == "" || this.properties["bgcolorBottom"] == "") - { - this.lineargradient = 0; - return; - } - - this.lineargradient = ctx.createLinearGradient(0,0,0,this.size[1]); - this.lineargradient.addColorStop(0,this.properties["bgcolorTop"]); - this.lineargradient.addColorStop(1,this.properties["bgcolorBottom"]); - } - - WidgetPanel.prototype.onDrawForeground = function(ctx) - { - if(this.lineargradient == null) - this.createGradient(ctx); - - if(!this.lineargradient) - return; - - ctx.lineWidth = 1; - ctx.strokeStyle = this.properties["borderColor"]; - //ctx.fillStyle = "#ebebeb"; - ctx.fillStyle = this.lineargradient; - - if(this.properties["shadowSize"]) - { - ctx.shadowColor = "#000"; - ctx.shadowOffsetX = 0; - ctx.shadowOffsetY = 0; - ctx.shadowBlur = this.properties["shadowSize"]; - } - else - ctx.shadowColor = "transparent"; - - ctx.roundRect(0,0,this.size[0]-1,this.size[1]-1,this.properties["shadowSize"]); - ctx.fill(); - ctx.shadowColor = "transparent"; - ctx.stroke(); - } - - WidgetPanel.prototype.onWidget = function(e,widget) - { - if(widget.name == "update") - { - this.lineargradient = null; - this.setDirtyCanvas(true); - } - } - - LiteGraph.registerNodeType("widget/panel", WidgetPanel ); - -})(this); -(function(global){ -var LiteGraph = global.LiteGraph; - -function GamepadInput() -{ - this.addOutput("left_x_axis","number"); - this.addOutput("left_y_axis","number"); - this.addOutput( "button_pressed", LiteGraph.EVENT ); - this.properties = { gamepad_index: 0, threshold: 0.1 }; - - this._left_axis = new Float32Array(2); - this._right_axis = new Float32Array(2); - this._triggers = new Float32Array(2); - this._previous_buttons = new Uint8Array(17); - this._current_buttons = new Uint8Array(17); -} - -GamepadInput.title = "Gamepad"; -GamepadInput.desc = "gets the input of the gamepad"; - -GamepadInput.zero = new Float32Array(2); -GamepadInput.buttons = ["a","b","x","y","lb","rb","lt","rt","back","start","ls","rs","home"]; - -GamepadInput.prototype.onExecute = function() -{ - //get gamepad - var gamepad = this.getGamepad(); - var threshold = this.properties.threshold || 0.0; - - if(gamepad) - { - this._left_axis[0] = Math.abs( gamepad.xbox.axes["lx"] ) > threshold ? gamepad.xbox.axes["lx"] : 0; - this._left_axis[1] = Math.abs( gamepad.xbox.axes["ly"] ) > threshold ? gamepad.xbox.axes["ly"] : 0; - this._right_axis[0] = Math.abs( gamepad.xbox.axes["rx"] ) > threshold ? gamepad.xbox.axes["rx"] : 0; - this._right_axis[1] = Math.abs( gamepad.xbox.axes["ry"] ) > threshold ? gamepad.xbox.axes["ry"] : 0; - this._triggers[0] = Math.abs( gamepad.xbox.axes["ltrigger"] ) > threshold ? gamepad.xbox.axes["ltrigger"] : 0; - this._triggers[1] = Math.abs( gamepad.xbox.axes["rtrigger"] ) > threshold ? gamepad.xbox.axes["rtrigger"] : 0; - } - - if(this.outputs) - { - for(var i = 0; i < this.outputs.length; i++) - { - var output = this.outputs[i]; - if(!output.links || !output.links.length) - continue; - var v = null; - - if(gamepad) - { - switch( output.name ) - { - case "left_axis": v = this._left_axis; break; - case "right_axis": v = this._right_axis; break; - case "left_x_axis": v = this._left_axis[0]; break; - case "left_y_axis": v = this._left_axis[1]; break; - case "right_x_axis": v = this._right_axis[0]; break; - case "right_y_axis": v = this._right_axis[1]; break; - case "trigger_left": v = this._triggers[0]; break; - case "trigger_right": v = this._triggers[1]; break; - case "a_button": v = gamepad.xbox.buttons["a"] ? 1 : 0; break; - case "b_button": v = gamepad.xbox.buttons["b"] ? 1 : 0; break; - case "x_button": v = gamepad.xbox.buttons["x"] ? 1 : 0; break; - case "y_button": v = gamepad.xbox.buttons["y"] ? 1 : 0; break; - case "lb_button": v = gamepad.xbox.buttons["lb"] ? 1 : 0; break; - case "rb_button": v = gamepad.xbox.buttons["rb"] ? 1 : 0; break; - case "ls_button": v = gamepad.xbox.buttons["ls"] ? 1 : 0; break; - case "rs_button": v = gamepad.xbox.buttons["rs"] ? 1 : 0; break; - case "start_button": v = gamepad.xbox.buttons["start"] ? 1 : 0; break; - case "back_button": v = gamepad.xbox.buttons["back"] ? 1 : 0; break; - case "button_pressed": - for(var j = 0; j < this._current_buttons.length; ++j) - { - if( this._current_buttons[j] && !this._previous_buttons[j] ) - this.triggerSlot( i, GamepadInput.buttons[j] ); - } - break; - default: break; - } - } - else - { - //if no gamepad is connected, output 0 - switch( output.name ) - { - case "button_pressed": break; - case "left_axis": - case "right_axis": - v = GamepadInput.zero; - break; - default: - v = 0; - } - } - this.setOutputData(i,v); - } - } -} - -GamepadInput.prototype.getGamepad = function() -{ - var getGamepads = navigator.getGamepads || navigator.webkitGetGamepads || navigator.mozGetGamepads; - if(!getGamepads) - return null; - var gamepads = getGamepads.call(navigator); - var gamepad = null; - - this._previous_buttons.set( this._current_buttons ); - - //pick the first connected - for(var i = this.properties.gamepad_index; i < 4; i++) - { - if (gamepads[i]) - { - gamepad = gamepads[i]; - - //xbox controller mapping - var xbox = this.xbox_mapping; - if(!xbox) - xbox = this.xbox_mapping = { axes:[], buttons:{}, hat: ""}; - - xbox.axes["lx"] = gamepad.axes[0]; - xbox.axes["ly"] = gamepad.axes[1]; - xbox.axes["rx"] = gamepad.axes[2]; - xbox.axes["ry"] = gamepad.axes[3]; - xbox.axes["ltrigger"] = gamepad.buttons[6].value; - xbox.axes["rtrigger"] = gamepad.buttons[7].value; - - for(var j = 0; j < gamepad.buttons.length; j++) - { - this._current_buttons[j] = gamepad.buttons[j].pressed; - - //mapping of XBOX - switch(j) //I use a switch to ensure that a player with another gamepad could play - { - case 0: xbox.buttons["a"] = gamepad.buttons[j].pressed; break; - case 1: xbox.buttons["b"] = gamepad.buttons[j].pressed; break; - case 2: xbox.buttons["x"] = gamepad.buttons[j].pressed; break; - case 3: xbox.buttons["y"] = gamepad.buttons[j].pressed; break; - case 4: xbox.buttons["lb"] = gamepad.buttons[j].pressed; break; - case 5: xbox.buttons["rb"] = gamepad.buttons[j].pressed; break; - case 6: xbox.buttons["lt"] = gamepad.buttons[j].pressed; break; - case 7: xbox.buttons["rt"] = gamepad.buttons[j].pressed; break; - case 8: xbox.buttons["back"] = gamepad.buttons[j].pressed; break; - case 9: xbox.buttons["start"] = gamepad.buttons[j].pressed; break; - case 10: xbox.buttons["ls"] = gamepad.buttons[j].pressed; break; - case 11: xbox.buttons["rs"] = gamepad.buttons[j].pressed; break; - case 12: if( gamepad.buttons[j].pressed) xbox.hat += "up"; break; - case 13: if( gamepad.buttons[j].pressed) xbox.hat += "down"; break; - case 14: if( gamepad.buttons[j].pressed) xbox.hat += "left"; break; - case 15: if( gamepad.buttons[j].pressed) xbox.hat += "right"; break; - case 16: xbox.buttons["home"] = gamepad.buttons[j].pressed; break; - default: - } - } - gamepad.xbox = xbox; - return gamepad; - } - } -} - -GamepadInput.prototype.onDrawBackground = function(ctx) -{ - //render gamepad state? - var la = this._left_axis; - var ra = this._right_axis; - ctx.strokeStyle = "#88A"; - ctx.strokeRect( (la[0] + 1) * 0.5 * this.size[0] - 4, (la[1] + 1) * 0.5 * this.size[1] - 4, 8, 8 ); - ctx.strokeStyle = "#8A8"; - ctx.strokeRect( (ra[0] + 1) * 0.5 * this.size[0] - 4, (ra[1] + 1) * 0.5 * this.size[1] - 4, 8, 8 ); - var h = this.size[1] / this._current_buttons.length - ctx.fillStyle = "#AEB"; - for(var i = 0; i < this._current_buttons.length; ++i) - if(this._current_buttons[i]) - ctx.fillRect( 0, h * i, 6, h); -} - -GamepadInput.prototype.onGetOutputs = function() { - return [ - ["left_axis","vec2"], - ["right_axis","vec2"], - ["left_x_axis","number"], - ["left_y_axis","number"], - ["right_x_axis","number"], - ["right_y_axis","number"], - ["trigger_left","number"], - ["trigger_right","number"], - ["a_button","number"], - ["b_button","number"], - ["x_button","number"], - ["y_button","number"], - ["lb_button","number"], - ["rb_button","number"], - ["ls_button","number"], - ["rs_button","number"], - ["start","number"], - ["back","number"], - ["button_pressed", LiteGraph.EVENT] - ]; -} - -LiteGraph.registerNodeType("input/gamepad", GamepadInput ); - -})(this); -(function(global){ -var LiteGraph = global.LiteGraph; - -//Converter -function Converter() -{ - this.addInput("in","*"); - this.size = [60,20]; -} - -Converter.title = "Converter"; -Converter.desc = "type A to type B"; - -Converter.prototype.onExecute = function() -{ - var v = this.getInputData(0); - if(v == null) - return; - - if(this.outputs) - for(var i = 0; i < this.outputs.length; i++) - { - var output = this.outputs[i]; - if(!output.links || !output.links.length) - continue; - - var result = null; - switch( output.name ) - { - case "number": result = v.length ? v[0] : parseFloat(v); break; - case "vec2": - case "vec3": - case "vec4": - var result = null; - var count = 1; - switch(output.name) - { - case "vec2": count = 2; break; - case "vec3": count = 3; break; - case "vec4": count = 4; break; - } - - var result = new Float32Array( count ); - if( v.length ) - { - for(var j = 0; j < v.length && j < result.length; j++) - result[j] = v[j]; - } - else - result[0] = parseFloat(v); - break; - } - this.setOutputData(i, result); - } -} - -Converter.prototype.onGetOutputs = function() { - return [["number","number"],["vec2","vec2"],["vec3","vec3"],["vec4","vec4"]]; -} - -LiteGraph.registerNodeType("math/converter", Converter ); - - -//Bypass -function Bypass() -{ - this.addInput("in"); - this.addOutput("out"); - this.size = [60,20]; -} - -Bypass.title = "Bypass"; -Bypass.desc = "removes the type"; - -Bypass.prototype.onExecute = function() -{ - var v = this.getInputData(0); - this.setOutputData(0, v); -} - -LiteGraph.registerNodeType("math/bypass", Bypass ); - - - -function MathRange() -{ - this.addInput("in","number",{locked:true}); - this.addOutput("out","number",{locked:true}); - - this.addProperty( "in", 0 ); - this.addProperty( "in_min", 0 ); - this.addProperty( "in_max", 1 ); - this.addProperty( "out_min", 0 ); - this.addProperty( "out_max", 1 ); -} - -MathRange.title = "Range"; -MathRange.desc = "Convert a number from one range to another"; - -MathRange.prototype.onExecute = function() -{ - if(this.inputs) - for(var i = 0; i < this.inputs.length; i++) - { - var input = this.inputs[i]; - var v = this.getInputData(i); - if(v === undefined) - continue; - this.properties[ input.name ] = v; - } - - var v = this.properties["in"]; - if(v === undefined || v === null || v.constructor !== Number) - v = 0; - - var in_min = this.properties.in_min; - var in_max = this.properties.in_max; - var out_min = this.properties.out_min; - var out_max = this.properties.out_max; - - this._last_v = ((v - in_min) / (in_max - in_min)) * (out_max - out_min) + out_min; - this.setOutputData(0, this._last_v ); -} - -MathRange.prototype.onDrawBackground = function(ctx) -{ - //show the current value - if(this._last_v) - this.outputs[0].label = this._last_v.toFixed(3); - else - this.outputs[0].label = "?"; -} - -MathRange.prototype.onGetInputs = function() { - return [["in_min","number"],["in_max","number"],["out_min","number"],["out_max","number"]]; -} - -LiteGraph.registerNodeType("math/range", MathRange); - - - -function MathRand() -{ - this.addOutput("value","number"); - this.addProperty( "min", 0 ); - this.addProperty( "max", 1 ); - this.size = [60,20]; -} - -MathRand.title = "Rand"; -MathRand.desc = "Random number"; - -MathRand.prototype.onExecute = function() -{ - if(this.inputs) - for(var i = 0; i < this.inputs.length; i++) - { - var input = this.inputs[i]; - var v = this.getInputData(i); - if(v === undefined) - continue; - this.properties[input.name] = v; - } - - var min = this.properties.min; - var max = this.properties.max; - this._last_v = Math.random() * (max-min) + min; - this.setOutputData(0, this._last_v ); -} - -MathRand.prototype.onDrawBackground = function(ctx) -{ - //show the current value - if(this._last_v) - this.outputs[0].label = this._last_v.toFixed(3); - else - this.outputs[0].label = "?"; -} - -MathRand.prototype.onGetInputs = function() { - return [["min","number"],["max","number"]]; -} - -LiteGraph.registerNodeType("math/rand", MathRand); - -//Math clamp -function MathClamp() -{ - this.addInput("in","number"); - this.addOutput("out","number"); - this.size = [60,20]; - this.addProperty( "min", 0 ); - this.addProperty( "max", 1 ); -} - -MathClamp.title = "Clamp"; -MathClamp.desc = "Clamp number between min and max"; -MathClamp.filter = "shader"; - -MathClamp.prototype.onExecute = function() -{ - var v = this.getInputData(0); - if(v == null) return; - v = Math.max(this.properties.min,v); - v = Math.min(this.properties.max,v); - this.setOutputData(0, v ); -} - -MathClamp.prototype.getCode = function(lang) -{ - var code = ""; - if(this.isInputConnected(0)) - code += "clamp({{0}}," + this.properties.min + "," + this.properties.max + ")"; - return code; -} - -LiteGraph.registerNodeType("math/clamp", MathClamp ); - - - -//Math ABS -function MathLerp() -{ - this.properties = { f: 0.5 }; - this.addInput("A","number"); - this.addInput("B","number"); - - this.addOutput("out","number"); -} - -MathLerp.title = "Lerp"; -MathLerp.desc = "Linear Interpolation"; - -MathLerp.prototype.onExecute = function() -{ - var v1 = this.getInputData(0); - if(v1 == null) - v1 = 0; - var v2 = this.getInputData(1); - if(v2 == null) - v2 = 0; - - var f = this.properties.f; - - var _f = this.getInputData(2); - if(_f !== undefined) - f = _f; - - this.setOutputData(0, v1 * (1-f) + v2 * f ); -} - -MathLerp.prototype.onGetInputs = function() -{ - return [["f","number"]]; -} - -LiteGraph.registerNodeType("math/lerp", MathLerp); - - - -//Math ABS -function MathAbs() -{ - this.addInput("in","number"); - this.addOutput("out","number"); - this.size = [60,20]; -} - -MathAbs.title = "Abs"; -MathAbs.desc = "Absolute"; - -MathAbs.prototype.onExecute = function() -{ - var v = this.getInputData(0); - if(v == null) return; - this.setOutputData(0, Math.abs(v) ); -} - -LiteGraph.registerNodeType("math/abs", MathAbs); - - -//Math Floor -function MathFloor() -{ - this.addInput("in","number"); - this.addOutput("out","number"); - this.size = [60,20]; -} - -MathFloor.title = "Floor"; -MathFloor.desc = "Floor number to remove fractional part"; - -MathFloor.prototype.onExecute = function() -{ - var v = this.getInputData(0); - if(v == null) return; - this.setOutputData(0, Math.floor(v) ); -} - -LiteGraph.registerNodeType("math/floor", MathFloor ); - - -//Math frac -function MathFrac() -{ - this.addInput("in","number"); - this.addOutput("out","number"); - this.size = [60,20]; -} - -MathFrac.title = "Frac"; -MathFrac.desc = "Returns fractional part"; - -MathFrac.prototype.onExecute = function() -{ - var v = this.getInputData(0); - if(v == null) - return; - this.setOutputData(0, v%1 ); -} - -LiteGraph.registerNodeType("math/frac",MathFrac); - - -//Math Floor -function MathSmoothStep() -{ - this.addInput("in","number"); - this.addOutput("out","number"); - this.size = [60,20]; - this.properties = { A: 0, B: 1 }; -} - -MathSmoothStep.title = "Smoothstep"; -MathSmoothStep.desc = "Smoothstep"; - -MathSmoothStep.prototype.onExecute = function() -{ - var v = this.getInputData(0); - if(v === undefined) - return; - - var edge0 = this.properties.A; - var edge1 = this.properties.B; - - // Scale, bias and saturate x to 0..1 range - v = Math.clamp((v - edge0)/(edge1 - edge0), 0.0, 1.0); - // Evaluate polynomial - v = v*v*(3 - 2*v); - - this.setOutputData(0, v ); -} - -LiteGraph.registerNodeType("math/smoothstep", MathSmoothStep ); - -//Math scale -function MathScale() -{ - this.addInput("in","number",{label:""}); - this.addOutput("out","number",{label:""}); - this.size = [60,20]; - this.addProperty( "factor", 1 ); -} - -MathScale.title = "Scale"; -MathScale.desc = "v * factor"; - -MathScale.prototype.onExecute = function() -{ - var value = this.getInputData(0); - if(value != null) - this.setOutputData(0, value * this.properties.factor ); -} - -LiteGraph.registerNodeType("math/scale", MathScale ); - - -//Math Average -function MathAverageFilter() -{ - this.addInput("in","number"); - this.addOutput("out","number"); - this.size = [60,20]; - this.addProperty( "samples", 10 ); - this._values = new Float32Array(10); - this._current = 0; -} - -MathAverageFilter.title = "Average"; -MathAverageFilter.desc = "Average Filter"; - -MathAverageFilter.prototype.onExecute = function() -{ - var v = this.getInputData(0); - if(v == null) - v = 0; - - var num_samples = this._values.length; - - this._values[ this._current % num_samples ] = v; - this._current += 1; - if(this._current > num_samples) - this._current = 0; - - var avr = 0; - for(var i = 0; i < num_samples; ++i) - avr += this._values[i]; - - this.setOutputData( 0, avr / num_samples ); -} - -MathAverageFilter.prototype.onPropertyChanged = function( name, value ) -{ - if(value < 1) - value = 1; - this.properties.samples = Math.round(value); - var old = this._values; - - this._values = new Float32Array( this.properties.samples ); - if(old.length <= this._values.length ) - this._values.set(old); - else - this._values.set( old.subarray( 0, this._values.length ) ); -} - -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() -{ - this.addInput("A","number"); - this.addInput("B","number"); - this.addOutput("=","number"); - this.addProperty( "A", 1 ); - this.addProperty( "B", 1 ); - this.addProperty( "OP", "+", "enum", { values: MathOperation.values } ); -} - -MathOperation.values = ["+","-","*","/","%","^"]; - -MathOperation.title = "Operation"; -MathOperation.desc = "Easy math operators"; -MathOperation["@OP"] = { type:"enum", title: "operation", values: MathOperation.values }; - -MathOperation.prototype.getTitle = function() -{ - return "A " + this.properties.OP + " B"; -} - -MathOperation.prototype.setValue = function(v) -{ - if( typeof(v) == "string") v = parseFloat(v); - this.properties["value"] = v; -} - -MathOperation.prototype.onExecute = function() -{ - var A = this.getInputData(0); - var B = this.getInputData(1); - if(A!=null) - this.properties["A"] = A; - else - A = this.properties["A"]; - - if(B!=null) - this.properties["B"] = B; - else - B = this.properties["B"]; - - var result = 0; - switch(this.properties.OP) - { - case '+': result = A+B; break; - case '-': result = A-B; break; - case 'x': - case 'X': - case '*': result = A*B; break; - case '/': result = A/B; break; - case '%': result = A%B; break; - case '^': result = Math.pow(A,B); break; - default: - console.warn("Unknown operation: " + this.properties.OP); - } - this.setOutputData(0, result ); -} - -MathOperation.prototype.onDrawBackground = function(ctx) -{ - if(this.flags.collapsed) - return; - - ctx.font = "40px Arial"; - ctx.fillStyle = "#CCC"; - ctx.textAlign = "center"; - ctx.fillText(this.properties.OP, this.size[0] * 0.5, this.size[1] * 0.35 + LiteGraph.NODE_TITLE_HEIGHT ); - ctx.textAlign = "left"; -} - -LiteGraph.registerNodeType("math/operation", MathOperation ); - - -//Math compare -function MathCompare() -{ - this.addInput( "A","number" ); - this.addInput( "B","number" ); - this.addOutput("A==B","boolean"); - this.addOutput("A!=B","boolean"); - this.addProperty( "A", 0 ); - this.addProperty( "B", 0 ); -} - -MathCompare.title = "Compare"; -MathCompare.desc = "compares between two values"; - -MathCompare.prototype.onExecute = function() -{ - var A = this.getInputData(0); - var B = this.getInputData(1); - if(A !== undefined) - this.properties["A"] = A; - else - A = this.properties["A"]; - - if(B !== undefined) - this.properties["B"] = B; - else - B = this.properties["B"]; - - for(var i = 0, l = this.outputs.length; i < l; ++i) - { - var output = this.outputs[i]; - if(!output.links || !output.links.length) - continue; - switch( output.name ) - { - case "A==B": value = A==B; break; - case "A!=B": value = A!=B; break; - case "A>B": value = A>B; break; - case "A=B": value = A>=B; break; - } - this.setOutputData(i, value ); - } -}; - -MathCompare.prototype.onGetOutputs = function() -{ - return [["A==B","boolean"],["A!=B","boolean"],["A>B","boolean"],["A=B","boolean"],["A<=B","boolean"]]; -} - -LiteGraph.registerNodeType("math/compare",MathCompare); - -function MathCondition() -{ - this.addInput("A","number"); - this.addInput("B","number"); - this.addOutput("out","boolean"); - this.addProperty( "A", 1 ); - this.addProperty( "B", 1 ); - this.addProperty( "OP", ">", "string", { values: MathCondition.values } ); - - this.size = [60,40]; -} - -MathCondition.values = [">","<","==","!=","<=",">="]; -MathCondition["@OP"] = { type:"enum", title: "operation", values: MathCondition.values }; - -MathCondition.title = "Condition"; -MathCondition.desc = "evaluates condition between A and B"; - -MathCondition.prototype.onExecute = function() -{ - var A = this.getInputData(0); - if(A === undefined) - A = this.properties.A; - else - this.properties.A = A; - - var B = this.getInputData(1); - if(B === undefined) - B = this.properties.B; - else - this.properties.B = B; - - var result = true; - switch(this.properties.OP) - { - case ">": result = A>B; break; - case "<": result = A=": result = A>=B; break; - } - - this.setOutputData(0, result ); -} - -LiteGraph.registerNodeType("math/condition", MathCondition); - - -function MathAccumulate() -{ - this.addInput("inc","number"); - this.addOutput("total","number"); - this.addProperty( "increment", 1 ); - this.addProperty( "value", 0 ); -} - -MathAccumulate.title = "Accumulate"; -MathAccumulate.desc = "Increments a value every time"; - -MathAccumulate.prototype.onExecute = function() -{ - if(this.properties.value === null) - this.properties.value = 0; - - var inc = this.getInputData(0); - if(inc !== null) - this.properties.value += inc; - else - this.properties.value += this.properties.increment; - this.setOutputData(0, this.properties.value ); -} - -LiteGraph.registerNodeType("math/accumulate", MathAccumulate); - -//Math Trigonometry -function MathTrigonometry() -{ - this.addInput("v","number"); - this.addOutput("sin","number"); - - this.addProperty( "amplitude", 1 ); - this.addProperty( "offset", 0 ); - this.bgImageUrl = "nodes/imgs/icon-sin.png"; -} - -MathTrigonometry.title = "Trigonometry"; -MathTrigonometry.desc = "Sin Cos Tan"; -MathTrigonometry.filter = "shader"; - -MathTrigonometry.prototype.onExecute = function() -{ - var v = this.getInputData(0); - if(v == null) - v = 0; - var amplitude = this.properties["amplitude"]; - var slot = this.findInputSlot("amplitude"); - if(slot != -1) - amplitude = this.getInputData(slot); - var offset = this.properties["offset"]; - slot = this.findInputSlot("offset"); - if(slot != -1) - offset = this.getInputData(slot); - - for(var i = 0, l = this.outputs.length; i < l; ++i) - { - var output = this.outputs[i]; - switch( output.name ) - { - case "sin": value = Math.sin(v); break; - case "cos": value = Math.cos(v); break; - case "tan": value = Math.tan(v); break; - case "asin": value = Math.asin(v); break; - case "acos": value = Math.acos(v); break; - case "atan": value = Math.atan(v); break; - } - this.setOutputData(i, amplitude * value + offset); - } -} - -MathTrigonometry.prototype.onGetInputs = function() -{ - return [["v","number"],["amplitude","number"],["offset","number"]]; -} - - -MathTrigonometry.prototype.onGetOutputs = function() -{ - return [["sin","number"],["cos","number"],["tan","number"],["asin","number"],["acos","number"],["atan","number"]]; -} - - -LiteGraph.registerNodeType("math/trigonometry", MathTrigonometry ); - - - -//math library for safe math operations without eval -if(typeof(math) != undefined) -{ - function MathFormula() - { - this.addInputs("x","number"); - this.addInputs("y","number"); - this.addOutputs("","number"); - this.properties = {x:1.0, y:1.0, formula:"x+y"}; - } - - MathFormula.title = "Formula"; - MathFormula.desc = "Compute safe formula"; - - MathFormula.prototype.onExecute = function() - { - var x = this.getInputData(0); - var y = this.getInputData(1); - if(x != null) - this.properties["x"] = x; - else - x = this.properties["x"]; - - if(y!=null) - this.properties["y"] = y; - else - y = this.properties["y"]; - - var f = this.properties["formula"]; - var value = math.eval(f,{x:x,y:y,T: this.graph.globaltime }); - this.setOutputData(0, value ); - } - - MathFormula.prototype.onDrawBackground = function() - { - var f = this.properties["formula"]; - this.outputs[0].label = f; - } - - MathFormula.prototype.onGetOutputs = function() - { - return [["A-B","number"],["A*B","number"],["A/B","number"]]; - } - - LiteGraph.registerNodeType("math/formula", MathFormula ); -} - - -function Math3DVec2ToXYZ() -{ - this.addInput("vec2","vec2"); - this.addOutput("x","number"); - this.addOutput("y","number"); -} - -Math3DVec2ToXYZ.title = "Vec2->XY"; -Math3DVec2ToXYZ.desc = "vector 2 to components"; - -Math3DVec2ToXYZ.prototype.onExecute = function() -{ - var v = this.getInputData(0); - if(v == null) return; - - this.setOutputData( 0, v[0] ); - this.setOutputData( 1, v[1] ); -} - -LiteGraph.registerNodeType("math3d/vec2-to-xyz", Math3DVec2ToXYZ ); - - -function Math3DXYToVec2() -{ - this.addInputs([["x","number"],["y","number"]]); - this.addOutput("vec2","vec2"); - this.properties = {x:0, y:0}; - this._data = new Float32Array(2); -} - -Math3DXYToVec2.title = "XY->Vec2"; -Math3DXYToVec2.desc = "components to vector2"; - -Math3DXYToVec2.prototype.onExecute = function() -{ - var x = this.getInputData(0); - if(x == null) x = this.properties.x; - var y = this.getInputData(1); - if(y == null) y = this.properties.y; - - var data = this._data; - data[0] = x; - data[1] = y; - - this.setOutputData( 0, data ); -} - -LiteGraph.registerNodeType("math3d/xy-to-vec2", Math3DXYToVec2 ); - - - - -function Math3DVec3ToXYZ() -{ - this.addInput("vec3","vec3"); - this.addOutput("x","number"); - this.addOutput("y","number"); - this.addOutput("z","number"); -} - -Math3DVec3ToXYZ.title = "Vec3->XYZ"; -Math3DVec3ToXYZ.desc = "vector 3 to components"; - -Math3DVec3ToXYZ.prototype.onExecute = function() -{ - var v = this.getInputData(0); - if(v == null) return; - - this.setOutputData( 0, v[0] ); - this.setOutputData( 1, v[1] ); - this.setOutputData( 2, v[2] ); -} - -LiteGraph.registerNodeType("math3d/vec3-to-xyz", Math3DVec3ToXYZ ); - - -function Math3DXYZToVec3() -{ - this.addInputs([["x","number"],["y","number"],["z","number"]]); - this.addOutput("vec3","vec3"); - this.properties = {x:0, y:0, z:0}; - this._data = new Float32Array(3); -} - -Math3DXYZToVec3.title = "XYZ->Vec3"; -Math3DXYZToVec3.desc = "components to vector3"; - -Math3DXYZToVec3.prototype.onExecute = function() -{ - var x = this.getInputData(0); - if(x == null) x = this.properties.x; - var y = this.getInputData(1); - if(y == null) y = this.properties.y; - var z = this.getInputData(2); - if(z == null) z = this.properties.z; - - var data = this._data; - data[0] = x; - data[1] = y; - data[2] = z; - - this.setOutputData( 0, data ); -} - -LiteGraph.registerNodeType("math3d/xyz-to-vec3", Math3DXYZToVec3 ); - - - -function Math3DVec4ToXYZW() -{ - this.addInput("vec4","vec4"); - this.addOutput("x","number"); - this.addOutput("y","number"); - this.addOutput("z","number"); - this.addOutput("w","number"); -} - -Math3DVec4ToXYZW.title = "Vec4->XYZW"; -Math3DVec4ToXYZW.desc = "vector 4 to components"; - -Math3DVec4ToXYZW.prototype.onExecute = function() -{ - var v = this.getInputData(0); - if(v == null) return; - - this.setOutputData( 0, v[0] ); - this.setOutputData( 1, v[1] ); - this.setOutputData( 2, v[2] ); - this.setOutputData( 3, v[3] ); -} - -LiteGraph.registerNodeType("math3d/vec4-to-xyzw", Math3DVec4ToXYZW ); - - -function Math3DXYZWToVec4() -{ - this.addInputs([["x","number"],["y","number"],["z","number"],["w","number"]]); - this.addOutput("vec4","vec4"); - this.properties = {x:0, y:0, z:0, w:0}; - this._data = new Float32Array(4); -} - -Math3DXYZWToVec4.title = "XYZW->Vec4"; -Math3DXYZWToVec4.desc = "components to vector4"; - -Math3DXYZWToVec4.prototype.onExecute = function() -{ - var x = this.getInputData(0); - if(x == null) x = this.properties.x; - var y = this.getInputData(1); - if(y == null) y = this.properties.y; - var z = this.getInputData(2); - if(z == null) z = this.properties.z; - var w = this.getInputData(3); - if(w == null) w = this.properties.w; - - var data = this._data; - data[0] = x; - data[1] = y; - data[2] = z; - data[3] = w; - - this.setOutputData( 0, data ); -} - -LiteGraph.registerNodeType("math3d/xyzw-to-vec4", Math3DXYZWToVec4 ); - - - - -//if glMatrix is installed... -if(global.glMatrix) -{ - - function Math3DQuaternion() - { - this.addOutput("quat","quat"); - this.properties = { x:0, y:0, z:0, w: 1 }; - this._value = quat.create(); - } - - Math3DQuaternion.title = "Quaternion"; - Math3DQuaternion.desc = "quaternion"; - - Math3DQuaternion.prototype.onExecute = function() - { - this._value[0] = this.properties.x; - this._value[1] = this.properties.y; - this._value[2] = this.properties.z; - this._value[3] = this.properties.w; - this.setOutputData( 0, this._value ); - } - - LiteGraph.registerNodeType("math3d/quaternion", Math3DQuaternion ); - - - function Math3DRotation() - { - this.addInputs([["degrees","number"],["axis","vec3"]]); - this.addOutput("quat","quat"); - this.properties = { angle:90.0, axis: vec3.fromValues(0,1,0) }; - - this._value = quat.create(); - } - - Math3DRotation.title = "Rotation"; - Math3DRotation.desc = "quaternion rotation"; - - Math3DRotation.prototype.onExecute = function() - { - var angle = this.getInputData(0); - if(angle == null) angle = this.properties.angle; - var axis = this.getInputData(1); - if(axis == null) axis = this.properties.axis; - - var R = quat.setAxisAngle( this._value, axis, angle * 0.0174532925 ); - this.setOutputData( 0, R ); - } - - - LiteGraph.registerNodeType("math3d/rotation", Math3DRotation ); - - - //Math3D rotate vec3 - function Math3DRotateVec3() - { - this.addInputs([["vec3","vec3"],["quat","quat"]]); - this.addOutput("result","vec3"); - this.properties = { vec: [0,0,1] }; - } - - Math3DRotateVec3.title = "Rot. Vec3"; - Math3DRotateVec3.desc = "rotate a point"; - - Math3DRotateVec3.prototype.onExecute = function() - { - var vec = this.getInputData(0); - if(vec == null) vec = this.properties.vec; - var quat = this.getInputData(1); - if(quat == null) - this.setOutputData(vec); - else - this.setOutputData( 0, vec3.transformQuat( vec3.create(), vec, quat ) ); - } - - LiteGraph.registerNodeType("math3d/rotate_vec3", Math3DRotateVec3); - - - - function Math3DMultQuat() - { - this.addInputs( [["A","quat"],["B","quat"]] ); - this.addOutput( "A*B","quat" ); - - this._value = quat.create(); - } - - Math3DMultQuat.title = "Mult. Quat"; - Math3DMultQuat.desc = "rotate quaternion"; - - Math3DMultQuat.prototype.onExecute = function() - { - var A = this.getInputData(0); - if(A == null) return; - var B = this.getInputData(1); - if(B == null) return; - - var R = quat.multiply( this._value, A, B ); - this.setOutputData( 0, R ); - } - - LiteGraph.registerNodeType("math3d/mult-quat", Math3DMultQuat ); - - - function Math3DQuatSlerp() - { - this.addInputs( [["A","quat"],["B","quat"],["factor","number"]] ); - this.addOutput( "slerp","quat" ); - this.addProperty( "factor", 0.5 ); - - this._value = quat.create(); - } - - Math3DQuatSlerp.title = "Quat Slerp"; - Math3DQuatSlerp.desc = "quaternion spherical interpolation"; - - Math3DQuatSlerp.prototype.onExecute = function() - { - var A = this.getInputData(0); - if(A == null) - return; - var B = this.getInputData(1); - if(B == null) - return; - var factor = this.properties.factor; - if( this.getInputData(2) != null ) - factor = this.getInputData(2); - - var R = quat.slerp( this._value, A, B, factor ); - this.setOutputData( 0, R ); - } - - LiteGraph.registerNodeType("math3d/quat-slerp", Math3DQuatSlerp ); - -} //glMatrix - -})(this); -(function(global){ -var LiteGraph = global.LiteGraph; - -function Selector() -{ - this.addInput("sel","boolean"); - this.addOutput("value","number"); - this.properties = { A:0, B:1 }; - this.size = [60,20]; -} - -Selector.title = "Selector"; -Selector.desc = "outputs A if selector is true, B if selector is false"; - -Selector.prototype.onExecute = function() -{ - var cond = this.getInputData(0); - if(cond === undefined) - return; - - for(var i = 1; i < this.inputs.length; i++) - { - var input = this.inputs[i]; - var v = this.getInputData(i); - if(v === undefined) - continue; - this.properties[input.name] = v; - } - - var A = this.properties.A; - var B = this.properties.B; - this.setOutputData(0, cond ? A : B ); -} - -Selector.prototype.onGetInputs = function() { - return [["A",0],["B",0]]; -} - -LiteGraph.registerNodeType("logic/selector", Selector); - -})(this); -(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.addOutput("frame","image"); - this.properties = {"url":""}; -} - -GraphicsImage.title = "Image"; -GraphicsImage.desc = "Image loader"; -GraphicsImage.widgets = [{name:"load",text:"Load",type:"button"}]; - -GraphicsImage.supported_extensions = ["jpg","jpeg","png","gif"]; - -GraphicsImage.prototype.onAdded = function() -{ - if(this.properties["url"] != "" && this.img == null) - { - this.loadImage( this.properties["url"] ); - } -} - -GraphicsImage.prototype.onDrawBackground = function(ctx) -{ - if(this.img && this.size[0] > 5 && this.size[1] > 5) - ctx.drawImage(this.img, 0,0,this.size[0],this.size[1]); -} - - -GraphicsImage.prototype.onExecute = function() -{ - if(!this.img) - this.boxcolor = "#000"; - if(this.img && this.img.width) - this.setOutputData(0,this.img); - else - this.setOutputData(0,null); - if(this.img && this.img.dirty) - this.img.dirty = false; -} - -GraphicsImage.prototype.onPropertyChanged = function(name,value) -{ - this.properties[name] = value; - if (name == "url" && value != "") - this.loadImage(value); - - return true; -} - -GraphicsImage.prototype.loadImage = function( url, callback ) -{ - if(url == "") - { - this.img = null; - return; - } - - this.img = document.createElement("img"); - - if(url.substr(0,4) == "http" && LiteGraph.proxy) - url = LiteGraph.proxy + url.substr( url.indexOf(":") + 3 ); - - this.img.src = url; - this.boxcolor = "#F95"; - var that = this; - this.img.onload = function() - { - if(callback) - callback(this); - that.trace("Image loaded, size: " + that.img.width + "x" + that.img.height ); - this.dirty = true; - that.boxcolor = "#9F9"; - that.setDirtyCanvas(true); - } -} - -GraphicsImage.prototype.onWidget = function(e,widget) -{ - if(widget.name == "load") - { - this.loadImage(this.properties["url"]); - } -} - -GraphicsImage.prototype.onDropFile = function(file) -{ - var that = this; - if(this._url) - URL.revokeObjectURL( this._url ); - this._url = URL.createObjectURL( file ); - this.properties.url = this._url; - this.loadImage( this._url, function(img){ - that.size[1] = (img.height / img.width) * that.size[0]; - }); -} - -LiteGraph.registerNodeType("graphics/image", GraphicsImage); - - - -function ColorPalette() -{ - this.addInput("f","number"); - this.addOutput("Color","color"); - this.properties = {colorA:"#444444",colorB:"#44AAFF",colorC:"#44FFAA",colorD:"#FFFFFF"}; - -} - -ColorPalette.title = "Palette"; -ColorPalette.desc = "Generates a color"; - -ColorPalette.prototype.onExecute = function() -{ - var c = []; - - if (this.properties.colorA != null) - c.push( hex2num( this.properties.colorA ) ); - if (this.properties.colorB != null) - c.push( hex2num( this.properties.colorB ) ); - if (this.properties.colorC != null) - c.push( hex2num( this.properties.colorC ) ); - if (this.properties.colorD != null) - c.push( hex2num( this.properties.colorD ) ); - - var f = this.getInputData(0); - if(f == null) f = 0.5; - if (f > 1.0) - f = 1.0; - else if (f < 0.0) - f = 0.0; - - if(c.length == 0) - return; - - var result = [0,0,0]; - if(f == 0) - result = c[0]; - else if(f == 1) - result = c[ c.length - 1]; - else - { - var pos = (c.length - 1)* f; - var c1 = c[ Math.floor(pos) ]; - var c2 = c[ Math.floor(pos)+1 ]; - var t = pos - Math.floor(pos); - result[0] = c1[0] * (1-t) + c2[0] * (t); - result[1] = c1[1] * (1-t) + c2[1] * (t); - result[2] = c1[2] * (1-t) + c2[2] * (t); - } - - /* - c[0] = 1.0 - Math.abs( Math.sin( 0.1 * reModular.getTime() * Math.PI) ); - c[1] = Math.abs( Math.sin( 0.07 * reModular.getTime() * Math.PI) ); - c[2] = Math.abs( Math.sin( 0.01 * reModular.getTime() * Math.PI) ); - */ - - for(var i in result) - result[i] /= 255; - - this.boxcolor = colorToString(result); - this.setOutputData(0, result); -} - - -LiteGraph.registerNodeType("color/palette", ColorPalette ); - - -function ImageFrame() -{ - this.addInput("","image"); - this.size = [200,200]; -} - -ImageFrame.title = "Frame"; -ImageFrame.desc = "Frame viewerew"; -ImageFrame.widgets = [{name:"resize",text:"Resize box",type:"button"},{name:"view",text:"View Image",type:"button"}]; - - -ImageFrame.prototype.onDrawBackground = function(ctx) -{ - if(this.frame) - ctx.drawImage(this.frame, 0,0,this.size[0],this.size[1]); -} - -ImageFrame.prototype.onExecute = function() -{ - this.frame = this.getInputData(0); - this.setDirtyCanvas(true); -} - -ImageFrame.prototype.onWidget = function(e,widget) -{ - if(widget.name == "resize" && this.frame) - { - var width = this.frame.width; - var height = this.frame.height; - - if(!width && this.frame.videoWidth != null ) - { - width = this.frame.videoWidth; - height = this.frame.videoHeight; - } - - if(width && height) - this.size = [width, height]; - this.setDirtyCanvas(true,true); - } - else if(widget.name == "view") - this.show(); -} - -ImageFrame.prototype.show = function() -{ - //var str = this.canvas.toDataURL("image/png"); - if(showElement && this.frame) - showElement(this.frame); -} - - -LiteGraph.registerNodeType("graphics/frame", ImageFrame ); - - - -/* -LiteGraph.registerNodeType("visualization/graph", { - desc: "Shows a graph of the inputs", - - inputs: [["",0],["",0],["",0],["",0]], - size: [200,200], - properties: {min:-1,max:1,bgColor:"#000"}, - onDrawBackground: function(ctx) - { - var colors = ["#FFF","#FAA","#AFA","#AAF"]; - - if(this.properties.bgColor != null && this.properties.bgColor != "") - { - ctx.fillStyle="#000"; - ctx.fillRect(2,2,this.size[0] - 4, this.size[1]-4); - } - - if(this.data) - { - var min = this.properties["min"]; - var max = this.properties["max"]; - - for(var i in this.data) - { - var data = this.data[i]; - if(!data) continue; - - if(this.getInputInfo(i) == null) continue; - - ctx.strokeStyle = colors[i]; - ctx.beginPath(); - - var d = data.length / this.size[0]; - for(var j = 0; j < data.length; j += d) - { - var value = data[ Math.floor(j) ]; - value = (value - min) / (max - min); - if (value > 1.0) value = 1.0; - else if(value < 0) value = 0; - - if(j == 0) - ctx.moveTo( j / d, (this.size[1] - 5) - (this.size[1] - 10) * value); - else - ctx.lineTo( j / d, (this.size[1] - 5) - (this.size[1] - 10) * value); - } - - ctx.stroke(); - } - } - - //ctx.restore(); - }, - - onExecute: function() - { - if(!this.data) this.data = []; - - for(var i in this.inputs) - { - var value = this.getInputData(i); - - if(typeof(value) == "number") - { - value = value ? value : 0; - if(!this.data[i]) - this.data[i] = []; - this.data[i].push(value); - - if(this.data[i].length > (this.size[1] - 4)) - this.data[i] = this.data[i].slice(1,this.data[i].length); - } - else - this.data[i] = value; - } - - if(this.data.length) - this.setDirtyCanvas(true); - } - }); -*/ - -function ImageFade() -{ - this.addInputs([["img1","image"],["img2","image"],["fade","number"]]); - this.addOutput("","image"); - this.properties = {fade:0.5,width:512,height:512}; -} - -ImageFade.title = "Image fade"; -ImageFade.desc = "Fades between images"; -ImageFade.widgets = [{name:"resizeA",text:"Resize to A",type:"button"},{name:"resizeB",text:"Resize to B",type:"button"}]; - -ImageFade.prototype.onAdded = function() -{ - this.createCanvas(); - var ctx = this.canvas.getContext("2d"); - ctx.fillStyle = "#000"; - ctx.fillRect(0,0,this.properties["width"],this.properties["height"]); -} - -ImageFade.prototype.createCanvas = function() -{ - this.canvas = document.createElement("canvas"); - this.canvas.width = this.properties["width"]; - this.canvas.height = this.properties["height"]; -} - -ImageFade.prototype.onExecute = function() -{ - var ctx = this.canvas.getContext("2d"); - this.canvas.width = this.canvas.width; - - var A = this.getInputData(0); - if (A != null) - { - ctx.drawImage(A,0,0,this.canvas.width, this.canvas.height); - } - - var fade = this.getInputData(2); - if(fade == null) - fade = this.properties["fade"]; - else - this.properties["fade"] = fade; - - ctx.globalAlpha = fade; - var B = this.getInputData(1); - if (B != null) - { - ctx.drawImage(B,0,0,this.canvas.width, this.canvas.height); - } - ctx.globalAlpha = 1.0; - - this.setOutputData(0,this.canvas); - this.setDirtyCanvas(true); -} - -LiteGraph.registerNodeType("graphics/imagefade", ImageFade); - - - -function ImageCrop() -{ - this.addInput("","image"); - this.addOutput("","image"); - this.properties = {width:256,height:256,x:0,y:0,scale:1.0 }; - this.size = [50,20]; -} - -ImageCrop.title = "Crop"; -ImageCrop.desc = "Crop Image"; - -ImageCrop.prototype.onAdded = function() -{ - this.createCanvas(); -} - -ImageCrop.prototype.createCanvas = function() -{ - this.canvas = document.createElement("canvas"); - this.canvas.width = this.properties["width"]; - this.canvas.height = this.properties["height"]; -} - -ImageCrop.prototype.onExecute = function() -{ - var input = this.getInputData(0); - if(!input) - return; - - if(input.width) - { - var ctx = this.canvas.getContext("2d"); - - ctx.drawImage(input, -this.properties["x"],-this.properties["y"], input.width * this.properties["scale"], input.height * this.properties["scale"]); - this.setOutputData(0,this.canvas); - } - else - this.setOutputData(0,null); -} - -ImageCrop.prototype.onDrawBackground = function(ctx) -{ - if(this.flags.collapsed) - return; - if(this.canvas) - ctx.drawImage( this.canvas, 0,0,this.canvas.width,this.canvas.height, 0,0, this.size[0], this.size[1] ); -} - -ImageCrop.prototype.onPropertyChanged = function(name,value) -{ - this.properties[name] = value; - - if(name == "scale") - { - this.properties[name] = parseFloat(value); - if(this.properties[name] == 0) - { - this.trace("Error in scale"); - this.properties[name] = 1.0; - } - } - else - this.properties[name] = parseInt(value); - - this.createCanvas(); - - return true; -} - -LiteGraph.registerNodeType("graphics/cropImage", ImageCrop ); - - -function ImageVideo() -{ - this.addInput("t","number"); - this.addOutputs([["frame","image"],["t","number"],["d","number"]]); - this.properties = { url:"", use_proxy: true }; -} - -ImageVideo.title = "Video"; -ImageVideo.desc = "Video playback"; -ImageVideo.widgets = [{name:"play",text:"PLAY",type:"minibutton"},{name:"stop",text:"STOP",type:"minibutton"},{name:"demo",text:"Demo video",type:"button"},{name:"mute",text:"Mute video",type:"button"}]; - -ImageVideo.prototype.onExecute = function() -{ - if(!this.properties.url) - return; - - if(this.properties.url != this._video_url) - this.loadVideo(this.properties.url); - - if(!this._video || this._video.width == 0) - return; - - var t = this.getInputData(0); - if(t && t >= 0 && t <= 1.0) - { - this._video.currentTime = t * this._video.duration; - this._video.pause(); - } - - this._video.dirty = true; - this.setOutputData(0,this._video); - this.setOutputData(1,this._video.currentTime); - this.setOutputData(2,this._video.duration); - this.setDirtyCanvas(true); -} - -ImageVideo.prototype.onStart = function() -{ - this.play(); -} - -ImageVideo.prototype.onStop = function() -{ - this.stop(); -} - -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"; - - this._video.muted = true; - this._video.autoplay = true; - - var that = this; - this._video.addEventListener("loadedmetadata",function(e) { - //onload - that.trace("Duration: " + this.duration + " seconds"); - that.trace("Size: " + this.videoWidth + "," + this.videoHeight); - that.setDirtyCanvas(true); - this.width = this.videoWidth; - this.height = this.videoHeight; - }); - this._video.addEventListener("progress",function(e) { - //onload - //that.trace("loading..."); - }); - this._video.addEventListener("error",function(e) { - console.log("Error loading video: " + this.src); - that.trace("Error loading video: " + this.src); - if (this.error) { - switch (this.error.code) { - case this.error.MEDIA_ERR_ABORTED: - that.trace("You stopped the video."); - break; - case this.error.MEDIA_ERR_NETWORK: - that.trace("Network error - please try again later."); - break; - case this.error.MEDIA_ERR_DECODE: - that.trace("Video is broken.."); - break; - case this.error.MEDIA_ERR_SRC_NOT_SUPPORTED: - that.trace("Sorry, your browser can't play this video."); - break; - } - } - }); - - this._video.addEventListener("ended",function(e) { - that.trace("Ended."); - this.play(); //loop - }); - - //document.body.appendChild(this.video); -} - -ImageVideo.prototype.onPropertyChanged = function(name,value) -{ - this.properties[name] = value; - if (name == "url" && value != "") - this.loadVideo(value); - - return true; -} - -ImageVideo.prototype.play = function() -{ - if(this._video) - this._video.play(); -} - -ImageVideo.prototype.playPause = function() -{ - if(!this._video) - return; - if(this._video.paused) - this.play(); - else - this.pause(); -} - -ImageVideo.prototype.stop = function() -{ - if(!this._video) - return; - this._video.pause(); - this._video.currentTime = 0; -} - -ImageVideo.prototype.pause = function() -{ - if(!this._video) - return; - this.trace("Video paused"); - this._video.pause(); -} - -ImageVideo.prototype.onWidget = function(e,widget) -{ - /* - if(widget.name == "demo") - { - this.loadVideo(); - } - else if(widget.name == "play") - { - if(this._video) - this.playPause(); - } - if(widget.name == "stop") - { - this.stop(); - } - else if(widget.name == "mute") - { - if(this._video) - this._video.muted = !this._video.muted; - } - */ -} - -LiteGraph.registerNodeType("graphics/video", ImageVideo ); - - -// Texture Webcam ***************************************** -function ImageWebcam() -{ - this.addOutput("Webcam","image"); - this.properties = {}; -} - -ImageWebcam.title = "Webcam"; -ImageWebcam.desc = "Webcam image"; - - -ImageWebcam.prototype.openStream = function() -{ - //Vendor prefixes hell - navigator.getUserMedia = (navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia); - window.URL = window.URL || window.webkitURL; - - if (!navigator.getUserMedia) { - //console.log('getUserMedia() is not supported in your browser, use chrome and enable WebRTC from about://flags'); - return; - } - - this._waiting_confirmation = true; - - // Not showing vendor prefixes. - navigator.getUserMedia({video: true}, this.streamReady.bind(this), onFailSoHard); - - var that = this; - function onFailSoHard(e) { - console.log('Webcam rejected', e); - that._webcam_stream = false; - that.box_color = "red"; - }; -} - -ImageWebcam.prototype.onRemoved = function() -{ - if(this._webcam_stream) - { - this._webcam_stream.stop(); - this._webcam_stream = null; - this._video = null; - } -} - -ImageWebcam.prototype.streamReady = function(localMediaStream) -{ - this._webcam_stream = localMediaStream; - //this._waiting_confirmation = false; - - var video = this._video; - if(!video) - { - video = document.createElement("video"); - video.autoplay = true; - video.src = window.URL.createObjectURL(localMediaStream); - this._video = video; - //document.body.appendChild( video ); //debug - //when video info is loaded (size and so) - video.onloadedmetadata = function(e) { - // Ready to go. Do some stuff. - console.log(e); - }; - } -}, - -ImageWebcam.prototype.onExecute = function() -{ - if(this._webcam_stream == null && !this._waiting_confirmation) - this.openStream(); - - if(!this._video || !this._video.videoWidth) return; - - this._video.width = this._video.videoWidth; - this._video.height = this._video.videoHeight; - this.setOutputData(0, this._video); -} - -ImageWebcam.prototype.getExtraMenuOptions = function(graphcanvas) -{ - var that = this; - var txt = !that.properties.show ? "Show Frame" : "Hide Frame"; - return [ {content: txt, callback: - function() { - that.properties.show = !that.properties.show; - } - }]; -} - -ImageWebcam.prototype.onDrawBackground = function(ctx) -{ - if(this.flags.collapsed || this.size[1] <= 20 || !this.properties.show) - return; - - if(!this._video) - return; - - //render to graph canvas - ctx.save(); - ctx.drawImage(this._video, 0, 0, this.size[0], this.size[1]); - ctx.restore(); -} - -LiteGraph.registerNodeType("graphics/webcam", ImageWebcam ); - - -})(this); - -(function(global){ -var LiteGraph = global.LiteGraph; - -//Works with Litegl.js to create WebGL nodes -global.LGraphTexture = null; - -if(typeof(GL) != "undefined") -{ - function LGraphTexture() - { - this.addOutput("Texture","Texture"); - this.properties = { name:"", filter: true }; - this.size = [LGraphTexture.image_preview_size, LGraphTexture.image_preview_size]; - } - - global.LGraphTexture = LGraphTexture; - - LGraphTexture.title = "Texture"; - LGraphTexture.desc = "Texture"; - LGraphTexture.widgets_info = {"name": { widget:"texture"}, "filter": { widget:"checkbox"} }; - - //REPLACE THIS TO INTEGRATE WITH YOUR FRAMEWORK - LGraphTexture.loadTextureCallback = null; //function in charge of loading textures when not present in the container - LGraphTexture.image_preview_size = 256; - - //flags to choose output texture type - LGraphTexture.PASS_THROUGH = 1; //do not apply FX - LGraphTexture.COPY = 2; //create new texture with the same properties as the origin texture - LGraphTexture.LOW = 3; //create new texture with low precision (byte) - LGraphTexture.HIGH = 4; //create new texture with high precision (half-float) - LGraphTexture.REUSE = 5; //reuse input texture - LGraphTexture.DEFAULT = 2; - - LGraphTexture.MODE_VALUES = { - "pass through": LGraphTexture.PASS_THROUGH, - "copy": LGraphTexture.COPY, - "low": LGraphTexture.LOW, - "high": LGraphTexture.HIGH, - "reuse": LGraphTexture.REUSE, - "default": LGraphTexture.DEFAULT - }; - - //returns the container where all the loaded textures are stored (overwrite if you have a Resources Manager) - LGraphTexture.getTexturesContainer = function() - { - return gl.textures; - } - - //process the loading of a texture (overwrite it if you have a Resources Manager) - LGraphTexture.loadTexture = function(name, options) - { - options = options || {}; - var url = name; - if(url.substr(0,7) == "http://") - { - if(LiteGraph.proxy) //proxy external files - url = LiteGraph.proxy + url.substr(7); - } - - var container = LGraphTexture.getTexturesContainer(); - var tex = container[ name ] = GL.Texture.fromURL(url, options); - return tex; - } - - LGraphTexture.getTexture = function(name) - { - var container = this.getTexturesContainer(); - - if(!container) - throw("Cannot load texture, container of textures not found"); - - var tex = container[ name ]; - if(!tex && name && name[0] != ":") - return this.loadTexture(name); - - return tex; - } - - //used to compute the appropiate output texture - LGraphTexture.getTargetTexture = function( origin, target, mode ) - { - if(!origin) - throw("LGraphTexture.getTargetTexture expects a reference texture"); - - var tex_type = null; - - switch(mode) - { - case LGraphTexture.LOW: tex_type = gl.UNSIGNED_BYTE; break; - case LGraphTexture.HIGH: tex_type = gl.HIGH_PRECISION_FORMAT; break; - case LGraphTexture.REUSE: return origin; break; - case LGraphTexture.COPY: - default: tex_type = origin ? origin.type : gl.UNSIGNED_BYTE; break; - } - - if(!target || target.width != origin.width || target.height != origin.height || target.type != tex_type ) - target = new GL.Texture( origin.width, origin.height, { type: tex_type, format: gl.RGBA, filter: gl.LINEAR }); - - return target; - } - - - LGraphTexture.getTextureType = function( precision, ref_texture ) - { - var type = ref_texture ? ref_texture.type : gl.UNSIGNED_BYTE; - switch( precision ) - { - case LGraphTexture.HIGH: type = gl.HIGH_PRECISION_FORMAT; break; - case LGraphTexture.LOW: type = gl.UNSIGNED_BYTE; break; - //no default - } - return type; - } - - LGraphTexture.getNoiseTexture = function() - { - if(this._noise_texture) - return this._noise_texture; - - var noise = new Uint8Array(512*512*4); - for(var i = 0; i < 512*512*4; ++i) - noise[i] = Math.random() * 255; - - var texture = GL.Texture.fromMemory(512,512,noise,{ format: gl.RGBA, wrap: gl.REPEAT, filter: gl.NEAREST }); - this._noise_texture = texture; - return texture; - } - - LGraphTexture.prototype.onDropFile = function(data, filename, file) - { - if(!data) - { - this._drop_texture = null; - this.properties.name = ""; - } - else - { - var texture = null; - if( typeof(data) == "string" ) - texture = GL.Texture.fromURL( data ); - else if( filename.toLowerCase().indexOf(".dds") != -1 ) - texture = GL.Texture.fromDDSInMemory(data); - else - { - var blob = new Blob([file]); - var url = URL.createObjectURL(blob); - texture = GL.Texture.fromURL( url ); - } - - this._drop_texture = texture; - this.properties.name = filename; - } - } - - LGraphTexture.prototype.getExtraMenuOptions = function(graphcanvas) - { - var that = this; - if(!this._drop_texture) - return; - return [ {content:"Clear", callback: - function() { - that._drop_texture = null; - that.properties.name = ""; - } - }]; - } - - LGraphTexture.prototype.onExecute = function() - { - var tex = null; - if(this.isOutputConnected(1)) - tex = this.getInputData(0); - - if(!tex && this._drop_texture) - tex = this._drop_texture; - - if(!tex && this.properties.name) - tex = LGraphTexture.getTexture( this.properties.name ); - - if(!tex) - return; - - this._last_tex = tex; - - if(this.properties.filter === false) - tex.setParameter( gl.TEXTURE_MAG_FILTER, gl.NEAREST ); - else - tex.setParameter( gl.TEXTURE_MAG_FILTER, gl.LINEAR ); - - this.setOutputData(0, tex); - - for(var i = 1; i < this.outputs.length; i++) - { - var output = this.outputs[i]; - if(!output) - continue; - var v = null; - if(output.name == "width") - v = tex.width; - else if(output.name == "height") - v = tex.height; - else if(output.name == "aspect") - v = tex.width / tex.height; - this.setOutputData(i, v); - } - } - - LGraphTexture.prototype.onResourceRenamed = function(old_name,new_name) - { - if(this.properties.name == old_name) - this.properties.name = new_name; - } - - LGraphTexture.prototype.onDrawBackground = function(ctx) - { - if( this.flags.collapsed || this.size[1] <= 20 ) - return; - - if( this._drop_texture && ctx.webgl ) - { - ctx.drawImage( this._drop_texture, 0,0,this.size[0],this.size[1]); - //this._drop_texture.renderQuad(this.pos[0],this.pos[1],this.size[0],this.size[1]); - return; - } - - - //Different texture? then get it from the GPU - if(this._last_preview_tex != this._last_tex) - { - if(ctx.webgl) - { - this._canvas = this._last_tex; - } - else - { - var tex_canvas = LGraphTexture.generateLowResTexturePreview(this._last_tex); - if(!tex_canvas) - return; - - this._last_preview_tex = this._last_tex; - this._canvas = cloneCanvas(tex_canvas); - } - } - - if(!this._canvas) - return; - - //render to graph canvas - ctx.save(); - if(!ctx.webgl) //reverse image - { - ctx.translate(0,this.size[1]); - ctx.scale(1,-1); - } - ctx.drawImage(this._canvas,0,0,this.size[0],this.size[1]); - ctx.restore(); - } - - - //very slow, used at your own risk - LGraphTexture.generateLowResTexturePreview = function(tex) - { - if(!tex) - return null; - - var size = LGraphTexture.image_preview_size; - var temp_tex = tex; - - if(tex.format == gl.DEPTH_COMPONENT) - return null; //cannot generate from depth - - //Generate low-level version in the GPU to speed up - if(tex.width > size || tex.height > size) - { - temp_tex = this._preview_temp_tex; - if(!this._preview_temp_tex) - { - temp_tex = new GL.Texture(size,size, { minFilter: gl.NEAREST }); - this._preview_temp_tex = temp_tex; - } - - //copy - tex.copyTo(temp_tex); - tex = temp_tex; - } - - //create intermediate canvas with lowquality version - var tex_canvas = this._preview_canvas; - if(!tex_canvas) - { - tex_canvas = createCanvas(size,size); - this._preview_canvas = tex_canvas; - } - - if(temp_tex) - temp_tex.toCanvas(tex_canvas); - return tex_canvas; - } - - LGraphTexture.prototype.getResources = function(res) - { - res[ this.properties.name ] = GL.Texture; - return res; - } - - LGraphTexture.prototype.onGetInputs = function() - { - return [["in","Texture"]]; - } - - - LGraphTexture.prototype.onGetOutputs = function() - { - return [["width","number"],["height","number"],["aspect","number"]]; - } - - LiteGraph.registerNodeType("texture/texture", LGraphTexture ); - - //************************** - function LGraphTexturePreview() - { - this.addInput("Texture","Texture"); - this.properties = { flipY: false }; - this.size = [LGraphTexture.image_preview_size, LGraphTexture.image_preview_size]; - } - - LGraphTexturePreview.title = "Preview"; - LGraphTexturePreview.desc = "Show a texture in the graph canvas"; - LGraphTexturePreview.allow_preview = false; - - LGraphTexturePreview.prototype.onDrawBackground = function(ctx) - { - if(this.flags.collapsed) - return; - - if(!ctx.webgl && !LGraphTexturePreview.allow_preview) - return; //not working well - - var tex = this.getInputData(0); - if(!tex) - return; - - var tex_canvas = null; - - if(!tex.handle && ctx.webgl) - tex_canvas = tex; - else - tex_canvas = LGraphTexture.generateLowResTexturePreview(tex); - - //render to graph canvas - ctx.save(); - if(this.properties.flipY) - { - ctx.translate(0,this.size[1]); - ctx.scale(1,-1); - } - ctx.drawImage(tex_canvas,0,0,this.size[0],this.size[1]); - ctx.restore(); - } - - LiteGraph.registerNodeType("texture/preview", LGraphTexturePreview ); - - //************************************** - - function LGraphTextureSave() - { - this.addInput("Texture","Texture"); - this.addOutput("","Texture"); - this.properties = {name:""}; - } - - LGraphTextureSave.title = "Save"; - LGraphTextureSave.desc = "Save a texture in the repository"; - - LGraphTextureSave.prototype.onExecute = function() - { - var tex = this.getInputData(0); - if(!tex) - return; - - if(this.properties.name) - { - //for cases where we want to perform something when storing it - if( LGraphTexture.storeTexture ) - LGraphTexture.storeTexture( this.properties.name, tex ); - else - { - var container = LGraphTexture.getTexturesContainer(); - container[ this.properties.name ] = tex; - } - } - - this.setOutputData(0, tex); - } - - LiteGraph.registerNodeType("texture/save", LGraphTextureSave ); - - //**************************************************** - - function LGraphTextureOperation() - { - this.addInput("Texture","Texture"); - this.addInput("TextureB","Texture"); - this.addInput("value","number"); - this.addOutput("Texture","Texture"); - this.help = "

pixelcode must be vec3

\ -

uvcode must be vec2, is optional

\ -

uv: tex. coords

color: texture

colorB: textureB

time: scene time

value: input value

"; - - this.properties = {value:1, uvcode:"", pixelcode:"color + colorB * value", precision: LGraphTexture.DEFAULT }; - } - - LGraphTextureOperation.widgets_info = { - "uvcode": { widget:"textarea", height: 100 }, - "pixelcode": { widget:"textarea", height: 100 }, - "precision": { widget:"combo", values: LGraphTexture.MODE_VALUES } - }; - - LGraphTextureOperation.title = "Operation"; - LGraphTextureOperation.desc = "Texture shader operation"; - - LGraphTextureOperation.prototype.getExtraMenuOptions = function(graphcanvas) - { - var that = this; - var txt = !that.properties.show ? "Show Texture" : "Hide Texture"; - return [ {content: txt, callback: - function() { - that.properties.show = !that.properties.show; - } - }]; - } - - LGraphTextureOperation.prototype.onDrawBackground = function(ctx) - { - if(this.flags.collapsed || this.size[1] <= 20 || !this.properties.show) - return; - - if(!this._tex) - return; - - //only works if using a webgl renderer - if(this._tex.gl != ctx) - return; - - //render to graph canvas - ctx.save(); - ctx.drawImage(this._tex, 0, 0, this.size[0], this.size[1]); - ctx.restore(); - } - - LGraphTextureOperation.prototype.onExecute = function() - { - var tex = this.getInputData(0); - - if(!this.isOutputConnected(0)) - return; //saves work - - if(this.properties.precision === LGraphTexture.PASS_THROUGH) - { - this.setOutputData(0, tex); - return; - } - - var texB = this.getInputData(1); - - if(!this.properties.uvcode && !this.properties.pixelcode) - return; - - var width = 512; - var height = 512; - if(tex) - { - width = tex.width; - height = tex.height; - } - else if (texB) - { - width = texB.width; - height = texB.height; - } - - var type = LGraphTexture.getTextureType( this.properties.precision, tex ); - - if(!tex && !this._tex ) - 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 ); - - var uvcode = ""; - if(this.properties.uvcode) - { - uvcode = "uv = " + this.properties.uvcode; - if(this.properties.uvcode.indexOf(";") != -1) //there are line breaks, means multiline code - uvcode = this.properties.uvcode; - } - - var pixelcode = ""; - if(this.properties.pixelcode) - { - pixelcode = "result = " + this.properties.pixelcode; - if(this.properties.pixelcode.indexOf(";") != -1) //there are line breaks, means multiline code - pixelcode = this.properties.pixelcode; - } - - var shader = this._shader; - - if(!shader || this._shader_code != (uvcode + "|" + pixelcode) ) - { - try - { - this._shader = new GL.Shader(Shader.SCREEN_VERTEX_SHADER, LGraphTextureOperation.pixel_shader, { UV_CODE: uvcode, PIXEL_CODE: pixelcode }); - this.boxcolor = "#00FF00"; - } - catch (err) - { - console.log("Error compiling shader: ", err); - this.boxcolor = "#FF0000"; - return; - } - this.boxcolor = "#FF0000"; - - this._shader_code = (uvcode + "|" + pixelcode); - shader = this._shader; - } - - if(!shader) - { - this.boxcolor = "red"; - return; - } - else - this.boxcolor = "green"; - - var value = this.getInputData(2); - if(value != null) - this.properties.value = value; - else - value = parseFloat( this.properties.value ); - - var time = this.graph.getTime(); - - this._tex.drawTo(function() { - gl.disable( gl.DEPTH_TEST ); - gl.disable( gl.CULL_FACE ); - gl.disable( gl.BLEND ); - if(tex) tex.bind(0); - if(texB) texB.bind(1); - var mesh = Mesh.getScreenQuad(); - shader.uniforms({u_texture:0, u_textureB:1, value: value, texSize:[width,height], time: time}).draw(mesh); - }); - - this.setOutputData(0, this._tex); - } - - LGraphTextureOperation.pixel_shader = "precision highp float;\n\ - \n\ - uniform sampler2D u_texture;\n\ - uniform sampler2D u_textureB;\n\ - varying vec2 v_coord;\n\ - uniform vec2 texSize;\n\ - uniform float time;\n\ - uniform float value;\n\ - \n\ - void main() {\n\ - vec2 uv = v_coord;\n\ - UV_CODE;\n\ - vec4 color4 = texture2D(u_texture, uv);\n\ - vec3 color = color4.rgb;\n\ - vec4 color4B = texture2D(u_textureB, uv);\n\ - vec3 colorB = color4B.rgb;\n\ - vec3 result = color;\n\ - float alpha = 1.0;\n\ - PIXEL_CODE;\n\ - gl_FragColor = vec4(result, alpha);\n\ - }\n\ - "; - - LiteGraph.registerNodeType("texture/operation", LGraphTextureOperation ); - - //**************************************************** - - function LGraphTextureShader() - { - this.addOutput("out","Texture"); - 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 = { in_texture:0, texSize: vec2.create(), time: 0 }; - } - - LGraphTextureShader.title = "Shader"; - LGraphTextureShader.desc = "Texture shader"; - LGraphTextureShader.widgets_info = { - "code": { type:"code" }, - "precision": { widget:"combo", values: LGraphTexture.MODE_VALUES } - }; - - LGraphTextureShader.prototype.onPropertyChanged = function(name, value) - { - if(name != "code") - return; - - var shader = this.getShader(); - if(!shader) - return; - - //update connections - var uniforms = shader.uniformInfo; - - //remove deprecated slots - if(this.inputs) - { - var already = {}; - for(var i = 0; i < this.inputs.length; ++i) - { - var info = this.getInputInfo(i); - if(!info) - continue; - - if( uniforms[ info.name ] && !already[ info.name ] ) - { - already[ info.name ] = true; - continue; - } - this.removeInput(i); - i--; - } - } - - //update existing ones - for(var i in uniforms) - { - var info = shader.uniformInfo[i]; - if(info.loc === null) - continue; //is an attribute, not a uniform - if(i == "time") //default one - continue; - - var type = "number"; - if( this._shader.samplers[i] ) - type = "texture"; - else - { - switch(info.size) - { - case 1: type = "number"; break; - 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: continue; - } - } - - var slot = this.findInputSlot(i); - if(slot == -1) - { - this.addInput(i,type); - continue; - } - - var input_info = this.getInputInfo(slot); - if(!input_info) - this.addInput(i,type); - else - { - if(input_info.type == type) - continue; - this.removeInput(slot,type); - this.addInput(i,type); - } - } - } - - LGraphTextureShader.prototype.getShader = function() - { - //replug - if(this._shader && this._shader_code == this.properties.code) - return this._shader; - - this._shader_code = this.properties.code; - this._shader = new GL.Shader(Shader.SCREEN_VERTEX_SHADER, LGraphTextureShader.pixel_shader + this.properties.code ); - if(!this._shader) { - this.boxcolor = "red"; - return null; - } - else - this.boxcolor = "green"; - return this._shader; - } - - LGraphTextureShader.prototype.onExecute = function() - { - if(!this.isOutputConnected(0)) - return; //saves work - - var shader = this.getShader(); - if(!shader) - return; - - var tex_slot = 0; - var in_tex = null; - - //set uniforms - for(var i = 0; i < this.inputs.length; ++i) - { - var info = this.getInputInfo(i); - var data = this.getInputData(i); - if(data == null) - continue; - - if(data.constructor === GL.Texture) - { - data.bind(tex_slot); - if(!in_tex) - in_tex = data; - data = tex_slot; - tex_slot++; - } - shader.setUniform( info.name, data ); //data is tex_slot - } - - var uniforms = this._uniforms; - var type = LGraphTexture.getTextureType( this.properties.precision, in_tex ); - - //render to texture - var w = this.properties.width|0; - var h = this.properties.height|0; - if(w == 0) - w = in_tex ? in_tex.width : gl.canvas.width; - if(h == 0) - h = in_tex ? in_tex.height : gl.canvas.height; - uniforms.texSize[0] = w; - uniforms.texSize[1] = h; - uniforms.time = this.graph.getTime(); - - 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; - tex.drawTo(function() { - shader.uniforms( uniforms ).draw( GL.Mesh.getScreenQuad() ); - }); - - this.setOutputData( 0, this._tex ); - } - - LGraphTextureShader.pixel_shader = "precision highp float;\n\ - \n\ - varying vec2 v_coord;\n\ - uniform float time;\n\ - "; - - LiteGraph.registerNodeType("texture/shader", LGraphTextureShader ); - - // Texture Scale Offset - - function LGraphTextureScaleOffset() - { - this.addInput("in","Texture"); - this.addInput("scale","vec2"); - this.addInput("offset","vec2"); - this.addOutput("out","Texture"); - this.properties = { offset: vec2.fromValues(0,0), scale: vec2.fromValues(1,1), precision: LGraphTexture.DEFAULT }; - } - - LGraphTextureScaleOffset.widgets_info = { - "precision": { widget:"combo", values: LGraphTexture.MODE_VALUES } - }; - - LGraphTextureScaleOffset.title = "Scale/Offset"; - LGraphTextureScaleOffset.desc = "Applies an scaling and offseting"; - - LGraphTextureScaleOffset.prototype.onExecute = function() - { - var tex = this.getInputData(0); - - if(!this.isOutputConnected(0) || !tex) - return; //saves work - - if(this.properties.precision === LGraphTexture.PASS_THROUGH) - { - this.setOutputData(0, tex); - return; - } - - var width = tex.width; - var height = tex.height; - var type = this.precision === LGraphTexture.LOW ? gl.UNSIGNED_BYTE : gl.HIGH_PRECISION_FORMAT; - if (this.precision === LGraphTexture.DEFAULT) - type = tex.type; - - 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 shader = this._shader; - - if(!shader) - shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureScaleOffset.pixel_shader ); - - var scale = this.getInputData(1); - if(scale) - { - this.properties.scale[0] = scale[0]; - this.properties.scale[1] = scale[1]; - } - else - scale = this.properties.scale; - - var offset = this.getInputData(2); - if(offset) - { - this.properties.offset[0] = offset[0]; - this.properties.offset[1] = offset[1]; - } - else - offset = this.properties.offset; - - this._tex.drawTo(function() { - gl.disable( gl.DEPTH_TEST ); - gl.disable( gl.CULL_FACE ); - gl.disable( gl.BLEND ); - tex.bind(0); - var mesh = Mesh.getScreenQuad(); - shader.uniforms({u_texture:0, u_scale: scale, u_offset: offset}).draw( mesh ); - }); - - this.setOutputData( 0, this._tex ); - } - - LGraphTextureScaleOffset.pixel_shader = "precision highp float;\n\ - \n\ - uniform sampler2D u_texture;\n\ - uniform sampler2D u_textureB;\n\ - varying vec2 v_coord;\n\ - uniform vec2 u_scale;\n\ - uniform vec2 u_offset;\n\ - \n\ - void main() {\n\ - vec2 uv = v_coord;\n\ - uv = uv / u_scale - u_offset;\n\ - gl_FragColor = texture2D(u_texture, uv);\n\ - }\n\ - "; - - LiteGraph.registerNodeType("texture/scaleOffset", LGraphTextureScaleOffset ); - - - - // Warp (distort a texture) ************************* - - function LGraphTextureWarp() - { - this.addInput("in","Texture"); - this.addInput("warp","Texture"); - this.addInput("factor","number"); - this.addOutput("out","Texture"); - this.properties = { factor: 0.01, precision: LGraphTexture.DEFAULT }; - } - - LGraphTextureWarp.widgets_info = { - "precision": { widget:"combo", values: LGraphTexture.MODE_VALUES } - }; - - LGraphTextureWarp.title = "Warp"; - LGraphTextureWarp.desc = "Texture warp operation"; - - LGraphTextureWarp.prototype.onExecute = function() - { - var tex = this.getInputData(0); - - if(!this.isOutputConnected(0)) - return; //saves work - - if(this.properties.precision === LGraphTexture.PASS_THROUGH) - { - this.setOutputData(0, tex); - return; - } - - var texB = this.getInputData(1); - - 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; - } - - 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 }); - else - this._tex = LGraphTexture.getTargetTexture( tex || this._tex, this._tex, this.properties.precision ); - - var shader = this._shader; - - if(!shader) - shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureWarp.pixel_shader ); - - var factor = this.getInputData(2); - if(factor != null) - this.properties.factor = factor; - else - factor = parseFloat( this.properties.factor ); - - this._tex.drawTo(function() { - gl.disable( gl.DEPTH_TEST ); - gl.disable( gl.CULL_FACE ); - gl.disable( gl.BLEND ); - if(tex) tex.bind(0); - if(texB) texB.bind(1); - var mesh = Mesh.getScreenQuad(); - shader.uniforms({u_texture:0, u_textureB:1, u_factor: factor }).draw( mesh ); - }); - - this.setOutputData(0, this._tex); - } - - LGraphTextureWarp.pixel_shader = "precision highp float;\n\ - \n\ - uniform sampler2D u_texture;\n\ - uniform sampler2D u_textureB;\n\ - varying vec2 v_coord;\n\ - uniform float u_factor;\n\ - \n\ - void main() {\n\ - vec2 uv = v_coord;\n\ - uv += ( texture2D(u_textureB, uv).rg - vec2(0.5)) * u_factor;\n\ - gl_FragColor = texture2D(u_texture, uv);\n\ - }\n\ - "; - - LiteGraph.registerNodeType("texture/warp", LGraphTextureWarp ); - - //**************************************************** - - // Texture to Viewport ***************************************** - function LGraphTextureToViewport() - { - this.addInput("Texture","Texture"); - this.properties = { additive: false, antialiasing: false, filter: true, disable_alpha: false, gamma: 1.0 }; - this.size[0] = 130; - } - - LGraphTextureToViewport.title = "to Viewport"; - LGraphTextureToViewport.desc = "Texture to viewport"; - - LGraphTextureToViewport.prototype.onExecute = function() - { - var tex = this.getInputData(0); - if(!tex) - return; - - if(this.properties.disable_alpha) - gl.disable( gl.BLEND ); - else - { - gl.enable( gl.BLEND ); - if(this.properties.additive) - gl.blendFunc( gl.SRC_ALPHA, gl.ONE ); - else - gl.blendFunc( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA ); - } - - gl.disable( gl.DEPTH_TEST ); - var gamma = this.properties.gamma || 1.0; - if( this.isInputConnected(1) ) - gamma = this.getInputData(1); - - tex.setParameter( gl.TEXTURE_MAG_FILTER, this.properties.filter ? gl.LINEAR : gl.NEAREST ); - - if(this.properties.antialiasing) - { - if(!LGraphTextureToViewport._shader) - LGraphTextureToViewport._shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureToViewport.aa_pixel_shader ); - - var viewport = gl.getViewport(); //gl.getParameter(gl.VIEWPORT); - var mesh = Mesh.getScreenQuad(); - tex.bind(0); - LGraphTextureToViewport._shader.uniforms({u_texture:0, uViewportSize:[tex.width,tex.height], u_igamma: 1 / gamma, inverseVP: [1/tex.width,1/tex.height] }).draw(mesh); - } - else - { - if(gamma != 1.0) - { - if(!LGraphTextureToViewport._gamma_shader) - LGraphTextureToViewport._gamma_shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphTextureToViewport.gamma_pixel_shader ); - tex.toViewport(LGraphTextureToViewport._gamma_shader, { u_texture:0, u_igamma: 1 / gamma }); - } - else - tex.toViewport(); - } - } - - LGraphTextureToViewport.prototype.onGetInputs = function() - { - return [["gamma","number"]]; - } - - LGraphTextureToViewport.aa_pixel_shader = "precision highp float;\n\ - precision highp float;\n\ - varying vec2 v_coord;\n\ - uniform sampler2D u_texture;\n\ - uniform vec2 uViewportSize;\n\ - uniform vec2 inverseVP;\n\ - uniform float u_igamma;\n\ - #define FXAA_REDUCE_MIN (1.0/ 128.0)\n\ - #define FXAA_REDUCE_MUL (1.0 / 8.0)\n\ - #define FXAA_SPAN_MAX 8.0\n\ - \n\ - /* from mitsuhiko/webgl-meincraft based on the code on geeks3d.com */\n\ - vec4 applyFXAA(sampler2D tex, vec2 fragCoord)\n\ - {\n\ - vec4 color = vec4(0.0);\n\ - /*vec2 inverseVP = vec2(1.0 / uViewportSize.x, 1.0 / uViewportSize.y);*/\n\ - vec3 rgbNW = texture2D(tex, (fragCoord + vec2(-1.0, -1.0)) * inverseVP).xyz;\n\ - vec3 rgbNE = texture2D(tex, (fragCoord + vec2(1.0, -1.0)) * inverseVP).xyz;\n\ - vec3 rgbSW = texture2D(tex, (fragCoord + vec2(-1.0, 1.0)) * inverseVP).xyz;\n\ - vec3 rgbSE = texture2D(tex, (fragCoord + vec2(1.0, 1.0)) * inverseVP).xyz;\n\ - vec3 rgbM = texture2D(tex, fragCoord * inverseVP).xyz;\n\ - vec3 luma = vec3(0.299, 0.587, 0.114);\n\ - float lumaNW = dot(rgbNW, luma);\n\ - float lumaNE = dot(rgbNE, luma);\n\ - float lumaSW = dot(rgbSW, luma);\n\ - float lumaSE = dot(rgbSE, luma);\n\ - float lumaM = dot(rgbM, luma);\n\ - float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE)));\n\ - float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE)));\n\ - \n\ - vec2 dir;\n\ - dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE));\n\ - dir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE));\n\ - \n\ - float dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) * (0.25 * FXAA_REDUCE_MUL), FXAA_REDUCE_MIN);\n\ - \n\ - float rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce);\n\ - dir = min(vec2(FXAA_SPAN_MAX, FXAA_SPAN_MAX), max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX), dir * rcpDirMin)) * inverseVP;\n\ - \n\ - vec3 rgbA = 0.5 * (texture2D(tex, fragCoord * inverseVP + dir * (1.0 / 3.0 - 0.5)).xyz + \n\ - texture2D(tex, fragCoord * inverseVP + dir * (2.0 / 3.0 - 0.5)).xyz);\n\ - vec3 rgbB = rgbA * 0.5 + 0.25 * (texture2D(tex, fragCoord * inverseVP + dir * -0.5).xyz + \n\ - texture2D(tex, fragCoord * inverseVP + dir * 0.5).xyz);\n\ - \n\ - //return vec4(rgbA,1.0);\n\ - float lumaB = dot(rgbB, luma);\n\ - if ((lumaB < lumaMin) || (lumaB > lumaMax))\n\ - color = vec4(rgbA, 1.0);\n\ - else\n\ - color = vec4(rgbB, 1.0);\n\ - if(u_igamma != 1.0)\n\ - color.xyz = pow( color.xyz, vec3(u_igamma) );\n\ - return color;\n\ - }\n\ - \n\ - void main() {\n\ - gl_FragColor = applyFXAA( u_texture, v_coord * uViewportSize) ;\n\ - }\n\ - "; - - LGraphTextureToViewport.gamma_pixel_shader = "precision highp float;\n\ - precision highp float;\n\ - varying vec2 v_coord;\n\ - uniform sampler2D u_texture;\n\ - uniform float u_igamma;\n\ - void main() {\n\ - vec4 color = texture2D( u_texture, v_coord);\n\ - color.xyz = pow(color.xyz, vec3(u_igamma) );\n\ - gl_FragColor = color;\n\ - }\n\ - "; - - - LiteGraph.registerNodeType("texture/toviewport", LGraphTextureToViewport ); - - - // Texture Copy ***************************************** - function LGraphTextureCopy() - { - this.addInput("Texture","Texture"); - this.addOutput("","Texture"); - this.properties = { size: 0, generate_mipmaps: false, precision: LGraphTexture.DEFAULT }; - } - - LGraphTextureCopy.title = "Copy"; - LGraphTextureCopy.desc = "Copy Texture"; - LGraphTextureCopy.widgets_info = { - size: { widget:"combo", values:[0,32,64,128,256,512,1024,2048]}, - precision: { widget:"combo", values: LGraphTexture.MODE_VALUES } - }; - - LGraphTextureCopy.prototype.onExecute = function() - { - var tex = this.getInputData(0); - if(!tex && !this._temp_texture) - return; - - if(!this.isOutputConnected(0)) - return; //saves work - - //copy the texture - if(tex) - { - var width = tex.width; - var height = tex.height; - - if(this.properties.size != 0) - { - width = this.properties.size; - height = this.properties.size; - } - - var temp = this._temp_texture; - - var type = tex.type; - if(this.properties.precision === LGraphTexture.LOW) - type = gl.UNSIGNED_BYTE; - else if(this.properties.precision === LGraphTexture.HIGH) - type = gl.HIGH_PRECISION_FORMAT; - - if(!temp || temp.width != width || temp.height != height || temp.type != type ) - { - var minFilter = gl.LINEAR; - if( this.properties.generate_mipmaps && isPowerOfTwo(width) && isPowerOfTwo(height) ) - minFilter = gl.LINEAR_MIPMAP_LINEAR; - this._temp_texture = new GL.Texture( width, height, { type: type, format: gl.RGBA, minFilter: minFilter, magFilter: gl.LINEAR }); - } - tex.copyTo(this._temp_texture); - - if(this.properties.generate_mipmaps) - { - this._temp_texture.bind(0); - gl.generateMipmap(this._temp_texture.texture_type); - this._temp_texture.unbind(0); - } - } - - - this.setOutputData(0,this._temp_texture); - } - - LiteGraph.registerNodeType("texture/copy", LGraphTextureCopy ); - - - // Texture Downsample ***************************************** - function LGraphTextureDownsample() - { - this.addInput("Texture","Texture"); - this.addOutput("","Texture"); - this.properties = { iterations: 1, generate_mipmaps: false, precision: LGraphTexture.DEFAULT }; - } - - LGraphTextureDownsample.title = "Downsample"; - LGraphTextureDownsample.desc = "Downsample Texture"; - LGraphTextureDownsample.widgets_info = { - iterations: { type:"number", step: 1, precision: 0, min: 1 }, - precision: { widget:"combo", values: LGraphTexture.MODE_VALUES } - }; - - LGraphTextureDownsample.prototype.onExecute = function() - { - var tex = this.getInputData(0); - if(!tex && !this._temp_texture) - return; - - if(!this.isOutputConnected(0)) - return; //saves work - - //we do not allow any texture different than texture 2D - if(!tex || tex.texture_type !== GL.TEXTURE_2D ) - return; - - var shader = LGraphTextureDownsample._shader; - if(!shader) - LGraphTextureDownsample._shader = shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureDownsample.pixel_shader ); - - var width = tex.width|0; - var height = tex.height|0; - var type = tex.type; - if(this.properties.precision === LGraphTexture.LOW) - type = gl.UNSIGNED_BYTE; - else if(this.properties.precision === LGraphTexture.HIGH) - type = gl.HIGH_PRECISION_FORMAT; - var iterations = this.properties.iterations || 1; - - var origin = tex; - var target = null; - - var temp = []; - var options = { - type: type, - format: tex.format - }; - - var offset = vec2.create(); - var uniforms = { - u_offset: offset - }; - - if( this._texture ) - GL.Texture.releaseTemporary( this._texture ); - - for(var i = 0; i < iterations; ++i) - { - offset[0] = 1/width; - offset[1] = 1/height; - width = width>>1 || 0; - height = height>>1 || 0; - target = GL.Texture.getTemporary( width, height, options ); - temp.push( target ); - origin.setParameter( GL.TEXTURE_MAG_FILTER, GL.NEAREST ); - origin.copyTo( target, shader, uniforms ); - if(width == 1 && height == 1) - break; //nothing else to do - origin = target; - } - - //keep the last texture used - this._texture = temp.pop(); - - //free the rest - for(var i = 0; i < temp.length; ++i) - GL.Texture.releaseTemporary( temp[i] ); - - if(this.properties.generate_mipmaps) - { - this._texture.bind(0); - gl.generateMipmap(this._texture.texture_type); - this._texture.unbind(0); - } - - this.setOutputData(0,this._texture); - } - - LGraphTextureDownsample.pixel_shader = "precision highp float;\n\ - precision highp float;\n\ - uniform sampler2D u_texture;\n\ - uniform vec2 u_offset;\n\ - varying vec2 v_coord;\n\ - \n\ - void main() {\n\ - vec4 color = texture2D(u_texture, v_coord );\n\ - color += texture2D(u_texture, v_coord + vec2( u_offset.x, 0.0 ) );\n\ - color += texture2D(u_texture, v_coord + vec2( 0.0, u_offset.y ) );\n\ - color += texture2D(u_texture, v_coord + vec2( u_offset.x, u_offset.y ) );\n\ - gl_FragColor = color * 0.25;\n\ - }\n\ - "; - - LiteGraph.registerNodeType("texture/downsample", LGraphTextureDownsample ); - - - - // Texture Copy ***************************************** - function LGraphTextureAverage() - { - this.addInput("Texture","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"; - LGraphTextureAverage.desc = "Compute a partial average (32 random samples) of a texture and stores it as a 1x1 pixel texture"; - - LGraphTextureAverage.prototype.onExecute = function() - { - var tex = this.getInputData(0); - if(!tex) - return; - - if(!this.isOutputConnected(0) && !this.isOutputConnected(1) && !this.isOutputConnected(2)) - return; //saves work - - if(!LGraphTextureAverage._shader) - { - LGraphTextureAverage._shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureAverage.pixel_shader); - //creates 32 random numbers and stores the, in two mat4 - var samples = new Float32Array(32); - for(var i = 0; i < 32; ++i) - samples[i] = Math.random(); - LGraphTextureAverage._shader.uniforms({u_samples_a: samples.subarray(0,16), u_samples_b: samples.subarray(16,32) }); - } - - var temp = this._temp_texture; - 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 }); - - var shader = LGraphTextureAverage._shader; - var uniforms = this._uniforms; - uniforms.u_mipmap_offset = this.properties.mipmap_offset; - this._temp_texture.drawTo(function(){ - tex.toViewport( shader, uniforms ); - }); - - 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\ - precision highp float;\n\ - uniform mat4 u_samples_a;\n\ - uniform mat4 u_samples_b;\n\ - uniform sampler2D u_texture;\n\ - uniform float u_mipmap_offset;\n\ - varying vec2 v_coord;\n\ - \n\ - void main() {\n\ - vec4 color = vec4(0.0);\n\ - for(int i = 0; i < 4; ++i)\n\ - for(int j = 0; j < 4; ++j)\n\ - {\n\ - color += texture2D(u_texture, vec2( u_samples_a[i][j], u_samples_b[i][j] ), u_mipmap_offset );\n\ - color += texture2D(u_texture, vec2( 1.0 - u_samples_a[i][j], 1.0 - u_samples_b[i][j] ), u_mipmap_offset );\n\ - }\n\ - gl_FragColor = color * 0.03125;\n\ - }\n\ - "; - - LiteGraph.registerNodeType("texture/average", LGraphTextureAverage ); - - // Image To Texture ***************************************** - function LGraphImageToTexture() - { - this.addInput("Image","image"); - this.addOutput("","Texture"); - this.properties = {}; - } - - LGraphImageToTexture.title = "Image to Texture"; - LGraphImageToTexture.desc = "Uploads an image to the GPU"; - //LGraphImageToTexture.widgets_info = { size: { widget:"combo", values:[0,32,64,128,256,512,1024,2048]} }; - - LGraphImageToTexture.prototype.onExecute = function() - { - var img = this.getInputData(0); - if(!img) - return; - - var width = img.videoWidth || img.width; - var height = img.videoHeight || img.height; - - //this is in case we are using a webgl canvas already, no need to reupload it - if(img.gltexture) - { - this.setOutputData(0,img.gltexture); - return; - } - - - var temp = this._temp_texture; - if(!temp || temp.width != width || temp.height != height ) - this._temp_texture = new GL.Texture( width, height, { format: gl.RGBA, filter: gl.LINEAR }); - - try - { - this._temp_texture.uploadImage(img); - } - catch(err) - { - console.error("image comes from an unsafe location, cannot be uploaded to webgl: " + err); - return; - } - - this.setOutputData(0,this._temp_texture); - } - - LiteGraph.registerNodeType("texture/imageToTexture", LGraphImageToTexture ); - - - // Texture LUT ***************************************** - function LGraphTextureLUT() - { - this.addInput("Texture","Texture"); - this.addInput("LUT","Texture"); - this.addInput("Intensity","number"); - this.addOutput("","Texture"); - this.properties = { intensity: 1, precision: LGraphTexture.DEFAULT, texture: null }; - - if(!LGraphTextureLUT._shader) - LGraphTextureLUT._shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphTextureLUT.pixel_shader ); - } - - LGraphTextureLUT.widgets_info = { - "texture": { widget:"texture"}, - "precision": { widget:"combo", values: LGraphTexture.MODE_VALUES } - }; - - LGraphTextureLUT.title = "LUT"; - LGraphTextureLUT.desc = "Apply LUT to Texture"; - - LGraphTextureLUT.prototype.onExecute = function() - { - if(!this.isOutputConnected(0)) - return; //saves work - - var tex = this.getInputData(0); - - if(this.properties.precision === LGraphTexture.PASS_THROUGH ) - { - this.setOutputData(0,tex); - return; - } - - if(!tex) - return; - - var lut_tex = this.getInputData(1); - - if(!lut_tex) - lut_tex = LGraphTexture.getTexture( this.properties.texture ); - - if(!lut_tex) - { - this.setOutputData(0,tex); - return; - } - - lut_tex.bind(0); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR ); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE ); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE ); - gl.bindTexture(gl.TEXTURE_2D, null); - - var intensity = this.properties.intensity; - if( this.isInputConnected(2) ) - this.properties.intensity = intensity = this.getInputData(2); - - this._tex = LGraphTexture.getTargetTexture( tex, this._tex, this.properties.precision ); - - //var mesh = Mesh.getScreenQuad(); - - this._tex.drawTo(function() { - lut_tex.bind(1); - tex.toViewport( LGraphTextureLUT._shader, {u_texture:0, u_textureB:1, u_amount: intensity} ); - }); - - this.setOutputData(0,this._tex); - } - - LGraphTextureLUT.pixel_shader = "precision highp float;\n\ - precision highp float;\n\ - varying vec2 v_coord;\n\ - uniform sampler2D u_texture;\n\ - uniform sampler2D u_textureB;\n\ - uniform float u_amount;\n\ - \n\ - void main() {\n\ - lowp vec4 textureColor = clamp( texture2D(u_texture, v_coord), vec4(0.0), vec4(1.0) );\n\ - mediump float blueColor = textureColor.b * 63.0;\n\ - mediump vec2 quad1;\n\ - quad1.y = floor(floor(blueColor) / 8.0);\n\ - quad1.x = floor(blueColor) - (quad1.y * 8.0);\n\ - mediump vec2 quad2;\n\ - quad2.y = floor(ceil(blueColor) / 8.0);\n\ - quad2.x = ceil(blueColor) - (quad2.y * 8.0);\n\ - highp vec2 texPos1;\n\ - texPos1.x = (quad1.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r);\n\ - texPos1.y = 1.0 - ((quad1.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.g));\n\ - highp vec2 texPos2;\n\ - texPos2.x = (quad2.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r);\n\ - texPos2.y = 1.0 - ((quad2.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.g));\n\ - lowp vec4 newColor1 = texture2D(u_textureB, texPos1);\n\ - lowp vec4 newColor2 = texture2D(u_textureB, texPos2);\n\ - lowp vec4 newColor = mix(newColor1, newColor2, fract(blueColor));\n\ - gl_FragColor = vec4( mix( textureColor.rgb, newColor.rgb, u_amount), textureColor.w);\n\ - }\n\ - "; - - LiteGraph.registerNodeType("texture/LUT", LGraphTextureLUT ); - - // Texture Channels ***************************************** - function LGraphTextureChannels() - { - this.addInput("Texture","Texture"); - - this.addOutput("R","Texture"); - this.addOutput("G","Texture"); - this.addOutput("B","Texture"); - this.addOutput("A","Texture"); - - this.properties = {}; - if(!LGraphTextureChannels._shader) - LGraphTextureChannels._shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphTextureChannels.pixel_shader ); - } - - LGraphTextureChannels.title = "Texture to Channels"; - LGraphTextureChannels.desc = "Split texture channels"; - - LGraphTextureChannels.prototype.onExecute = function() - { - var texA = this.getInputData(0); - if(!texA) return; - - if(!this._channels) - this._channels = Array(4); - - var connections = 0; - for(var i = 0; i < 4; i++) - { - if(this.isOutputConnected(i)) - { - if(!this._channels[i] || this._channels[i].width != texA.width || this._channels[i].height != texA.height || this._channels[i].type != texA.type) - this._channels[i] = new GL.Texture( texA.width, texA.height, { type: texA.type, format: gl.RGBA, filter: gl.LINEAR }); - connections++; - } - else - this._channels[i] = null; - } - - if(!connections) - return; - - gl.disable( gl.BLEND ); - gl.disable( gl.DEPTH_TEST ); - - var mesh = Mesh.getScreenQuad(); - var shader = LGraphTextureChannels._shader; - var masks = [[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]]; - - for(var i = 0; i < 4; i++) - { - if(!this._channels[i]) - continue; - - this._channels[i].drawTo( function() { - texA.bind(0); - shader.uniforms({u_texture:0, u_mask: masks[i]}).draw(mesh); - }); - this.setOutputData(i, this._channels[i]); - } - } - - LGraphTextureChannels.pixel_shader = "precision highp float;\n\ - precision highp float;\n\ - varying vec2 v_coord;\n\ - uniform sampler2D u_texture;\n\ - uniform vec4 u_mask;\n\ - \n\ - void main() {\n\ - gl_FragColor = vec4( vec3( length( texture2D(u_texture, v_coord) * u_mask )), 1.0 );\n\ - }\n\ - "; - - LiteGraph.registerNodeType("texture/textureChannels", LGraphTextureChannels ); - - - // Texture Channels to Texture ***************************************** - function LGraphChannelsTexture() - { - this.addInput("R","Texture"); - this.addInput("G","Texture"); - this.addInput("B","Texture"); - this.addInput("A","Texture"); - - this.addOutput("Texture","Texture"); - - this.properties = {}; - if(!LGraphChannelsTexture._shader) - LGraphChannelsTexture._shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphChannelsTexture.pixel_shader ); - } - - LGraphChannelsTexture.title = "Channels to Texture"; - LGraphChannelsTexture.desc = "Split texture channels"; - - LGraphChannelsTexture.prototype.onExecute = function() - { - var tex = [ this.getInputData(0), - this.getInputData(1), - this.getInputData(2), - this.getInputData(3) ]; - - if(!tex[0] || !tex[1] || !tex[2] || !tex[3]) - return; - - gl.disable( gl.BLEND ); - gl.disable( gl.DEPTH_TEST ); - - var mesh = Mesh.getScreenQuad(); - var shader = LGraphChannelsTexture._shader; - - this._tex = LGraphTexture.getTargetTexture( tex[0], this._tex ); - - this._tex.drawTo( function() { - tex[0].bind(0); - tex[1].bind(1); - tex[2].bind(2); - tex[3].bind(3); - shader.uniforms({u_textureR:0, u_textureG:1, u_textureB:2, u_textureA:3 }).draw(mesh); - }); - this.setOutputData(0, this._tex); - } - - LGraphChannelsTexture.pixel_shader = "precision highp float;\n\ - precision highp float;\n\ - varying vec2 v_coord;\n\ - uniform sampler2D u_textureR;\n\ - uniform sampler2D u_textureG;\n\ - uniform sampler2D u_textureB;\n\ - uniform sampler2D u_textureA;\n\ - \n\ - void main() {\n\ - gl_FragColor = vec4( \ - texture2D(u_textureR, v_coord).r,\ - texture2D(u_textureG, v_coord).r,\ - texture2D(u_textureB, v_coord).r,\ - texture2D(u_textureA, v_coord).r);\n\ - }\n\ - "; - - LiteGraph.registerNodeType("texture/channelsTexture", LGraphChannelsTexture ); - - // Texture Channels to Texture ***************************************** - function LGraphTextureGradient() - { - this.addInput("A","color"); - this.addInput("B","color"); - this.addOutput("Texture","Texture"); - - this.properties = { angle: 0, scale: 1, A:[0,0,0], B:[1,1,1], texture_size:32 }; - if(!LGraphTextureGradient._shader) - LGraphTextureGradient._shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphTextureGradient.pixel_shader ); - - this._uniforms = { u_angle: 0, u_colorA: vec3.create(), u_colorB: vec3.create()}; - } - - LGraphTextureGradient.title = "Gradient"; - LGraphTextureGradient.desc = "Generates a gradient"; - LGraphTextureGradient["@A"] = { type:"color" }; - LGraphTextureGradient["@B"] = { type:"color" }; - LGraphTextureGradient["@texture_size"] = { type:"enum", values:[32,64,128,256,512] }; - - LGraphTextureGradient.prototype.onExecute = function() - { - gl.disable( gl.BLEND ); - gl.disable( gl.DEPTH_TEST ); - - var mesh = GL.Mesh.getScreenQuad(); - var shader = LGraphTextureGradient._shader; - - var A = this.getInputData(0); - if(!A) - A = this.properties.A; - var B = this.getInputData(1); - if(!B) - B = this.properties.B; - - //angle and scale - for(var i = 2; i < this.inputs.length; i++) - { - var input = this.inputs[i]; - var v = this.getInputData(i); - if(v === undefined) - continue; - this.properties[ input.name ] = v; - } - - var uniforms = this._uniforms; - this._uniforms.u_angle = this.properties.angle * DEG2RAD; - this._uniforms.u_scale = this.properties.scale; - vec3.copy( uniforms.u_colorA, A ); - vec3.copy( uniforms.u_colorB, B ); - - var size = parseInt( this.properties.texture_size ); - if(!this._tex || this._tex.width != size ) - this._tex = new GL.Texture( size, size, { format: gl.RGB, filter: gl.LINEAR }); - - this._tex.drawTo( function() { - shader.uniforms(uniforms).draw(mesh); - }); - this.setOutputData(0, this._tex); - } - - LGraphTextureGradient.prototype.onGetInputs = function() - { - return [["angle","number"],["scale","number"]]; - } - - LGraphTextureGradient.pixel_shader = "precision highp float;\n\ - precision highp float;\n\ - varying vec2 v_coord;\n\ - uniform float u_angle;\n\ - uniform float u_scale;\n\ - uniform vec3 u_colorA;\n\ - uniform vec3 u_colorB;\n\ - \n\ - vec2 rotate(vec2 v, float angle)\n\ - {\n\ - vec2 result;\n\ - float _cos = cos(angle);\n\ - float _sin = sin(angle);\n\ - result.x = v.x * _cos - v.y * _sin;\n\ - result.y = v.x * _sin + v.y * _cos;\n\ - return result;\n\ - }\n\ - void main() {\n\ - float f = (rotate(u_scale * (v_coord - vec2(0.5)), u_angle) + vec2(0.5)).x;\n\ - vec3 color = mix(u_colorA,u_colorB,clamp(f,0.0,1.0));\n\ - gl_FragColor = vec4(color,1.0);\n\ - }\n\ - "; - - LiteGraph.registerNodeType("texture/gradient", LGraphTextureGradient ); - - // Texture Mix ***************************************** - function LGraphTextureMix() - { - this.addInput("A","Texture"); - this.addInput("B","Texture"); - this.addInput("Mixer","Texture"); - - this.addOutput("Texture","Texture"); - this.properties = { precision: LGraphTexture.DEFAULT }; - - if(!LGraphTextureMix._shader) - LGraphTextureMix._shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphTextureMix.pixel_shader ); - } - - LGraphTextureMix.title = "Mix"; - LGraphTextureMix.desc = "Generates a texture mixing two textures"; - - LGraphTextureMix.widgets_info = { - "precision": { widget:"combo", values: LGraphTexture.MODE_VALUES } - }; - - LGraphTextureMix.prototype.onExecute = function() - { - var texA = this.getInputData(0); - - if(!this.isOutputConnected(0)) - return; //saves work - - if(this.properties.precision === LGraphTexture.PASS_THROUGH ) - { - this.setOutputData(0,texA); - return; - } - - var texB = this.getInputData(1); - var texMix = this.getInputData(2); - if(!texA || !texB || !texMix) return; - - this._tex = LGraphTexture.getTargetTexture( texA, this._tex, this.properties.precision ); - - gl.disable( gl.BLEND ); - gl.disable( gl.DEPTH_TEST ); - - var mesh = Mesh.getScreenQuad(); - var shader = LGraphTextureMix._shader; - - this._tex.drawTo( function() { - texA.bind(0); - texB.bind(1); - texMix.bind(2); - shader.uniforms({u_textureA:0,u_textureB:1,u_textureMix:2}).draw(mesh); - }); - - this.setOutputData(0, this._tex); - } - - LGraphTextureMix.pixel_shader = "precision highp float;\n\ - precision highp float;\n\ - varying vec2 v_coord;\n\ - uniform sampler2D u_textureA;\n\ - uniform sampler2D u_textureB;\n\ - uniform sampler2D u_textureMix;\n\ - \n\ - void main() {\n\ - gl_FragColor = mix( texture2D(u_textureA, v_coord), texture2D(u_textureB, v_coord), texture2D(u_textureMix, v_coord) );\n\ - }\n\ - "; - - LiteGraph.registerNodeType("texture/mix", LGraphTextureMix ); - - // Texture Edges detection ***************************************** - function LGraphTextureEdges() - { - this.addInput("Tex.","Texture"); - - this.addOutput("Edges","Texture"); - 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 ); - } - - LGraphTextureEdges.title = "Edges"; - LGraphTextureEdges.desc = "Detects edges"; - - LGraphTextureEdges.widgets_info = { - "precision": { widget:"combo", values: LGraphTexture.MODE_VALUES } - }; - - LGraphTextureEdges.prototype.onExecute = function() - { - if(!this.isOutputConnected(0)) - return; //saves work - - var tex = this.getInputData(0); - - if(this.properties.precision === LGraphTexture.PASS_THROUGH ) - { - this.setOutputData(0,tex); - return; - } - - if(!tex) return; - - this._tex = LGraphTexture.getTargetTexture( tex, this._tex, this.properties.precision ); - - gl.disable( gl.BLEND ); - gl.disable( gl.DEPTH_TEST ); - - var mesh = Mesh.getScreenQuad(); - 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_threshold: threshold, u_invert: invert ? 1 : 0}).draw(mesh); - }); - - this.setOutputData(0, this._tex); - } - - LGraphTextureEdges.pixel_shader = "precision highp float;\n\ - precision highp float;\n\ - varying vec2 v_coord;\n\ - uniform sampler2D u_texture;\n\ - 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\ - vec4 up = texture2D(u_texture, v_coord + u_isize * vec2(0.0,1.0) );\n\ - vec4 down = texture2D(u_texture, v_coord + u_isize * vec2(0.0,-1.0) );\n\ - vec4 left = texture2D(u_texture, v_coord + u_isize * vec2(1.0,0.0) );\n\ - vec4 right = texture2D(u_texture, v_coord + u_isize * vec2(-1.0,0.0) );\n\ - vec4 diff = abs(center - up) + abs(center - down) + abs(center - left) + abs(center - right);\n\ - diff *= u_factor;\n\ - if(u_invert == 1)\n\ - diff.xyz = vec3(1.0) - diff.xyz;\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\ - "; - - LiteGraph.registerNodeType("texture/edges", LGraphTextureEdges ); - - // Texture Depth ***************************************** - function LGraphTextureDepthRange() - { - this.addInput("Texture","Texture"); - this.addInput("Distance","number"); - this.addInput("Range","number"); - this.addOutput("Texture","Texture"); - this.properties = { distance:100, range: 50, only_depth: false, high_precision: false }; - this._uniforms = {u_texture:0, u_distance: 100, u_range: 50, u_camera_planes: null }; - } - - LGraphTextureDepthRange.title = "Depth Range"; - LGraphTextureDepthRange.desc = "Generates a texture with a depth range"; - - LGraphTextureDepthRange.prototype.onExecute = function() - { - if(!this.isOutputConnected(0)) - return; //saves work - - var tex = this.getInputData(0); - if(!tex) return; - - var precision = gl.UNSIGNED_BYTE; - if(this.properties.high_precision) - precision = gl.half_float_ext ? gl.HALF_FLOAT_OES : gl.FLOAT; - - if(!this._temp_texture || this._temp_texture.type != precision || - this._temp_texture.width != tex.width || this._temp_texture.height != tex.height) - this._temp_texture = new GL.Texture( tex.width, tex.height, { type: precision, format: gl.RGBA, filter: gl.LINEAR }); - - var uniforms = this._uniforms; - - //iterations - var distance = this.properties.distance; - if( this.isInputConnected(1) ) - { - distance = this.getInputData(1); - this.properties.distance = distance; - } - - var range = this.properties.range; - if( this.isInputConnected(2) ) - { - range = this.getInputData(2); - this.properties.range = range; - } - - uniforms.u_distance = distance; - uniforms.u_range = range; - - gl.disable( gl.BLEND ); - gl.disable( gl.DEPTH_TEST ); - var mesh = Mesh.getScreenQuad(); - if(!LGraphTextureDepthRange._shader) - { - LGraphTextureDepthRange._shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphTextureDepthRange.pixel_shader ); - LGraphTextureDepthRange._shader_onlydepth = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphTextureDepthRange.pixel_shader, { ONLY_DEPTH:""} ); - } - var shader = this.properties.only_depth ? LGraphTextureDepthRange._shader_onlydepth : LGraphTextureDepthRange._shader; - - //NEAR AND FAR PLANES - var planes = null; - if( tex.near_far_planes ) - planes = tex.near_far_planes; - else if( window.LS && LS.Renderer._main_camera ) - planes = LS.Renderer._main_camera._uniforms.u_camera_planes; - else - planes = [0.1,1000]; //hardcoded - uniforms.u_camera_planes = planes; - - - this._temp_texture.drawTo( function() { - tex.bind(0); - shader.uniforms( uniforms ).draw(mesh); - }); - - this._temp_texture.near_far_planes = planes; - this.setOutputData(0, this._temp_texture ); - } - - LGraphTextureDepthRange.pixel_shader = "precision highp float;\n\ - precision highp float;\n\ - varying vec2 v_coord;\n\ - uniform sampler2D u_texture;\n\ - uniform vec2 u_camera_planes;\n\ - uniform float u_distance;\n\ - uniform float u_range;\n\ - \n\ - float LinearDepth()\n\ - {\n\ - float zNear = u_camera_planes.x;\n\ - float zFar = u_camera_planes.y;\n\ - float depth = texture2D(u_texture, v_coord).x;\n\ - depth = depth * 2.0 - 1.0;\n\ - return zNear * (depth + 1.0) / (zFar + zNear - depth * (zFar - zNear));\n\ - }\n\ - \n\ - void main() {\n\ - float depth = LinearDepth();\n\ - #ifdef ONLY_DEPTH\n\ - gl_FragColor = vec4(depth);\n\ - #else\n\ - float diff = abs(depth * u_camera_planes.y - u_distance);\n\ - float dof = 1.0;\n\ - if(diff <= u_range)\n\ - dof = diff / u_range;\n\ - gl_FragColor = vec4(dof);\n\ - #endif\n\ - }\n\ - "; - - LiteGraph.registerNodeType("texture/depth_range", LGraphTextureDepthRange ); - - // Texture Blur ***************************************** - function LGraphTextureBlur() - { - this.addInput("Texture","Texture"); - this.addInput("Iterations","number"); - this.addInput("Intensity","number"); - this.addOutput("Blurred","Texture"); - 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() - { - var tex = this.getInputData(0); - if(!tex) - return; - - if(!this.isOutputConnected(0)) - return; //saves work - - 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 }); - temp = this._final_texture = new GL.Texture( tex.width, tex.height, { type: tex.type, format: gl.RGBA, filter: gl.LINEAR }); - } - - //iterations - var iterations = this.properties.iterations; - if( this.isInputConnected(1) ) - { - iterations = this.getInputData(1); - this.properties.iterations = iterations; - } - iterations = Math.min( Math.floor(iterations), LGraphTextureBlur.max_iterations ); - if(iterations == 0) //skip blurring - { - this.setOutputData(0, tex); - return; - } - - var intensity = this.properties.intensity; - if( this.isInputConnected(2) ) - { - intensity = this.getInputData(2); - this.properties.intensity = intensity; - } - - //blur sometimes needs an aspect correction - var aspect = LiteGraph.camera_aspect; - if(!aspect && window.gl !== undefined) - aspect = gl.canvas.height / gl.canvas.width; - if(!aspect) - aspect = 1; - aspect = this.properties.preserve_aspect ? aspect : 1; - - var scale = this.properties.scale || [1,1]; - 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, temp ); - } - - /* - LGraphTextureBlur.pixel_shader = "precision highp float;\n\ - precision highp float;\n\ - varying vec2 v_coord;\n\ - uniform sampler2D u_texture;\n\ - uniform vec2 u_offset;\n\ - uniform float u_intensity;\n\ - void main() {\n\ - vec4 sum = vec4(0.0);\n\ - vec4 center = texture2D(u_texture, v_coord);\n\ - sum += texture2D(u_texture, v_coord + u_offset * -4.0) * 0.05/0.98;\n\ - sum += texture2D(u_texture, v_coord + u_offset * -3.0) * 0.09/0.98;\n\ - 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\ - sum += center * 0.16/0.98;\n\ - sum += texture2D(u_texture, v_coord + u_offset * 4.0) * 0.05/0.98;\n\ - sum += texture2D(u_texture, v_coord + u_offset * 3.0) * 0.09/0.98;\n\ - 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\ - }\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() - { - this.addInput("Texture","Texture"); - this.addOutput("Filtered","Texture"); - this.properties = { intensity: 1, radius: 5 }; - } - - LGraphTextureKuwaharaFilter.title = "Kuwahara Filter"; - LGraphTextureKuwaharaFilter.desc = "Filters a texture giving an artistic oil canvas painting"; - - LGraphTextureKuwaharaFilter.max_radius = 10; - LGraphTextureKuwaharaFilter._shaders = []; - - LGraphTextureKuwaharaFilter.prototype.onExecute = function() - { - var tex = this.getInputData(0); - if(!tex) - return; - - if(!this.isOutputConnected(0)) - return; //saves work - - var temp = this._temp_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 }); - } - - //iterations - var radius = this.properties.radius; - radius = Math.min( Math.floor(radius), LGraphTextureKuwaharaFilter.max_radius ); - if(radius == 0) //skip blurring - { - this.setOutputData(0, tex); - return; - } - - var intensity = this.properties.intensity; - - //blur sometimes needs an aspect correction - var aspect = LiteGraph.camera_aspect; - if(!aspect && window.gl !== undefined) - aspect = gl.canvas.height / gl.canvas.width; - if(!aspect) - aspect = 1; - aspect = this.properties.preserve_aspect ? aspect : 1; - - if(!LGraphTextureKuwaharaFilter._shaders[ radius ]) - LGraphTextureKuwaharaFilter._shaders[ radius ] = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphTextureKuwaharaFilter.pixel_shader, { RADIUS: radius.toFixed(0) }); - - var shader = LGraphTextureKuwaharaFilter._shaders[ radius ]; - var mesh = GL.Mesh.getScreenQuad(); - tex.bind(0); - - this._temp_texture.drawTo( function() { - shader.uniforms({ u_texture: 0, u_intensity: intensity, u_resolution: [tex.width, tex.height], u_iResolution: [1/tex.width,1/tex.height]}).draw(mesh); - }); - - this.setOutputData(0, this._temp_texture); - } - -//from https://www.shadertoy.com/view/MsXSz4 -LGraphTextureKuwaharaFilter.pixel_shader = "\n\ - precision highp float;\n\ - varying vec2 v_coord;\n\ - uniform sampler2D u_texture;\n\ - uniform float u_intensity;\n\ - uniform vec2 u_resolution;\n\ - uniform vec2 u_iResolution;\n\ - #ifndef RADIUS\n\ - #define RADIUS 7\n\ - #endif\n\ - void main() {\n\ - \n\ - const int radius = RADIUS;\n\ - vec2 fragCoord = v_coord;\n\ - vec2 src_size = u_iResolution;\n\ - vec2 uv = v_coord;\n\ - float n = float((radius + 1) * (radius + 1));\n\ - int i;\n\ - int j;\n\ - vec3 m0 = vec3(0.0); vec3 m1 = vec3(0.0); vec3 m2 = vec3(0.0); vec3 m3 = vec3(0.0);\n\ - vec3 s0 = vec3(0.0); vec3 s1 = vec3(0.0); vec3 s2 = vec3(0.0); vec3 s3 = vec3(0.0);\n\ - vec3 c;\n\ - \n\ - for (int j = -radius; j <= 0; ++j) {\n\ - for (int i = -radius; i <= 0; ++i) {\n\ - c = texture2D(u_texture, uv + vec2(i,j) * src_size).rgb;\n\ - m0 += c;\n\ - s0 += c * c;\n\ - }\n\ - }\n\ - \n\ - for (int j = -radius; j <= 0; ++j) {\n\ - for (int i = 0; i <= radius; ++i) {\n\ - c = texture2D(u_texture, uv + vec2(i,j) * src_size).rgb;\n\ - m1 += c;\n\ - s1 += c * c;\n\ - }\n\ - }\n\ - \n\ - for (int j = 0; j <= radius; ++j) {\n\ - for (int i = 0; i <= radius; ++i) {\n\ - c = texture2D(u_texture, uv + vec2(i,j) * src_size).rgb;\n\ - m2 += c;\n\ - s2 += c * c;\n\ - }\n\ - }\n\ - \n\ - for (int j = 0; j <= radius; ++j) {\n\ - for (int i = -radius; i <= 0; ++i) {\n\ - c = texture2D(u_texture, uv + vec2(i,j) * src_size).rgb;\n\ - m3 += c;\n\ - s3 += c * c;\n\ - }\n\ - }\n\ - \n\ - float min_sigma2 = 1e+2;\n\ - m0 /= n;\n\ - s0 = abs(s0 / n - m0 * m0);\n\ - \n\ - float sigma2 = s0.r + s0.g + s0.b;\n\ - if (sigma2 < min_sigma2) {\n\ - min_sigma2 = sigma2;\n\ - gl_FragColor = vec4(m0, 1.0);\n\ - }\n\ - \n\ - m1 /= n;\n\ - s1 = abs(s1 / n - m1 * m1);\n\ - \n\ - sigma2 = s1.r + s1.g + s1.b;\n\ - if (sigma2 < min_sigma2) {\n\ - min_sigma2 = sigma2;\n\ - gl_FragColor = vec4(m1, 1.0);\n\ - }\n\ - \n\ - m2 /= n;\n\ - s2 = abs(s2 / n - m2 * m2);\n\ - \n\ - sigma2 = s2.r + s2.g + s2.b;\n\ - if (sigma2 < min_sigma2) {\n\ - min_sigma2 = sigma2;\n\ - gl_FragColor = vec4(m2, 1.0);\n\ - }\n\ - \n\ - m3 /= n;\n\ - s3 = abs(s3 / n - m3 * m3);\n\ - \n\ - sigma2 = s3.r + s3.g + s3.b;\n\ - if (sigma2 < min_sigma2) {\n\ - min_sigma2 = sigma2;\n\ - gl_FragColor = vec4(m3, 1.0);\n\ - }\n\ - }\n\ - "; - - LiteGraph.registerNodeType("texture/kuwahara", LGraphTextureKuwaharaFilter ); - - - // Texture Webcam ***************************************** - function LGraphTextureWebcam() - { - this.addOutput("Webcam","Texture"); - this.properties = { texture_name: "" }; - } - - LGraphTextureWebcam.title = "Webcam"; - LGraphTextureWebcam.desc = "Webcam texture"; - - - LGraphTextureWebcam.prototype.openStream = function() - { - //Vendor prefixes hell - navigator.getUserMedia = (navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia); - window.URL = window.URL || window.webkitURL; - - if (!navigator.getUserMedia) { - //console.log('getUserMedia() is not supported in your browser, use chrome and enable WebRTC from about://flags'); - return; - } - - this._waiting_confirmation = true; - var that = this; - - // Not showing vendor prefixes. - navigator.getUserMedia({video: true}, this.streamReady.bind(this), onFailSoHard); - - function onFailSoHard(e) { - console.log('Webcam rejected', e); - that._webcam_stream = false; - that.box_color = "red"; - }; - } - - LGraphTextureWebcam.prototype.streamReady = function(localMediaStream) - { - this._webcam_stream = localMediaStream; - //this._waiting_confirmation = false; - - var video = this._video; - if(!video) - { - video = document.createElement("video"); - video.autoplay = true; - video.src = window.URL.createObjectURL( localMediaStream ); - this._video = video; - //document.body.appendChild( video ); //debug - //when video info is loaded (size and so) - video.onloadedmetadata = function(e) { - // Ready to go. Do some stuff. - console.log(e); - }; - } - } - - LGraphTextureWebcam.prototype.onRemoved = function() - { - if(!this._webcam_stream) - return; - - var video_streams = this._webcam_stream.getVideoTracks(); - if(video_streams.length) - { - var webcam = video_streams[0]; - if( webcam.stop ) - webcam.stop(); - } - - this._webcam_stream = null; - this._video = null; - } - - LGraphTextureWebcam.prototype.onDrawBackground = function(ctx) - { - if(this.flags.collapsed || this.size[1] <= 20) - return; - - if(!this._video) - return; - - //render to graph canvas - ctx.save(); - if(!ctx.webgl) //reverse image - { - ctx.translate(0,this.size[1]); - ctx.scale(1,-1); - ctx.drawImage(this._video, 0, 0, this.size[0], this.size[1]); - } - else - { - if(this._temp_texture) - ctx.drawImage(this._temp_texture, 0, 0, this.size[0], this.size[1]); - } - ctx.restore(); - } - - LGraphTextureWebcam.prototype.onExecute = function() - { - if(this._webcam_stream == null && !this._waiting_confirmation) - this.openStream(); - - if(!this._video || !this._video.videoWidth) - return; - - var width = this._video.videoWidth; - var height = this._video.videoHeight; - - var temp = this._temp_texture; - if(!temp || temp.width != width || temp.height != height ) - this._temp_texture = new GL.Texture( width, height, { format: gl.RGB, filter: gl.LINEAR }); - - this._temp_texture.uploadImage( this._video ); - - if(this.properties.texture_name) - { - var container = LGraphTexture.getTexturesContainer(); - container[ this.properties.texture_name ] = this._temp_texture; - } - - this.setOutputData(0,this._temp_texture); - } - - 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; i= 0xF0) - this.cmd = midiStatus; - else - this.cmd = midiCommand; - - if(this.cmd == MIDIEvent.NOTEON && this.velocity == 0) - this.cmd = MIDIEvent.NOTEOFF; - - this.cmd_str = MIDIEvent.commands[ this.cmd ] || ""; - - if ( midiCommand >= MIDIEvent.NOTEON || midiCommand <= MIDIEvent.NOTEOFF ) { - this.channel = midiStatus & 0x0F; - } -} - -Object.defineProperty( MIDIEvent.prototype, "velocity", { - get: function() { - if(this.cmd == MIDIEvent.NOTEON) - return this.data[2]; - return -1; - }, - set: function(v) { - this.data[2] = v; // v / 127; - }, - enumerable: true -}); - -MIDIEvent.notes = ["A","A#","B","C","C#","D","D#","E","F","F#","G","G#"]; - -//returns HZs -MIDIEvent.prototype.getPitch = function() -{ - return Math.pow(2, (this.data[1] - 69) / 12 ) * 440; -} - -MIDIEvent.computePitch = function( note ) -{ - return Math.pow(2, (note - 69) / 12 ) * 440; -} - -MIDIEvent.prototype.getCC = function() -{ - return this.data[1]; -} - -MIDIEvent.prototype.getCCValue = function() -{ - return this.data[2]; -} - -//not tested, there is a formula missing here -MIDIEvent.prototype.getPitchBend = function() -{ - return this.data[1] + (this.data[2] << 7) - 8192; -} - -MIDIEvent.computePitchBend = function(v1,v2) -{ - return v1 + (v2 << 7) - 8192; -} - -MIDIEvent.prototype.setCommandFromString = function( str ) -{ - this.cmd = MIDIEvent.computeCommandFromString(str); -} - -MIDIEvent.computeCommandFromString = function( str ) -{ - if(!str) - return 0; - - if(str && str.constructor === Number) - return str; - - str = str.toUpperCase(); - switch( str ) - { - case "NOTE ON": - case "NOTEON": return MIDIEvent.NOTEON; break; - case "NOTE OFF": - case "NOTEOFF": return MIDIEvent.NOTEON; break; - case "KEY PRESSURE": - case "KEYPRESSURE": return MIDIEvent.KEYPRESSURE; break; - case "CONTROLLER CHANGE": - case "CONTROLLERCHANGE": - case "CC": return MIDIEvent.CONTROLLERCHANGE; break; - case "PROGRAM CHANGE": - case "PROGRAMCHANGE": - case "PC": return MIDIEvent.PROGRAMCHANGE; break; - case "CHANNEL PRESSURE": - case "CHANNELPRESSURE": return MIDIEvent.CHANNELPRESSURE; break; - case "PITCH BEND": - case "PITCHBEND": return MIDIEvent.PITCHBEND; break; - case "TIME TICK": - case "TIMETICK": return MIDIEvent.TIMETICK; break; - default: return Number(str); //asume its a hex code - } -} - -MIDIEvent.toNoteString = function(d) -{ - var note = d - 21; - var octave = d - 24; - note = note % 12; - if(note < 0) - note = 12 + note; - return MIDIEvent.notes[ note ] + Math.floor(octave / 12 + 1); -} - -MIDIEvent.prototype.toString = function() -{ - var str = "" + this.channel + ". " ; - switch( this.cmd ) - { - case MIDIEvent.NOTEON: str += "NOTEON " + MIDIEvent.toNoteString( this.data[1] ); break; - case MIDIEvent.NOTEOFF: str += "NOTEOFF " + MIDIEvent.toNoteString( this.data[1] ); break; - case MIDIEvent.CONTROLLERCHANGE: str += "CC " + this.data[1] + " " + this.data[2]; break; - case MIDIEvent.PROGRAMCHANGE: str += "PC " + this.data[1]; break; - case MIDIEvent.PITCHBEND: str += "PITCHBEND " + this.getPitchBend(); break; - case MIDIEvent.KEYPRESSURE: str += "KEYPRESS " + this.data[1]; break; - } - - return str; -} - -MIDIEvent.prototype.toHexString = function() -{ - var str = ""; - for(var i = 0; i < this.data.length; i++) - str += this.data[i].toString(16) + " "; -} - -MIDIEvent.NOTEOFF = 0x80; -MIDIEvent.NOTEON = 0x90; -MIDIEvent.KEYPRESSURE = 0xA0; -MIDIEvent.CONTROLLERCHANGE = 0xB0; -MIDIEvent.PROGRAMCHANGE = 0xC0; -MIDIEvent.CHANNELPRESSURE = 0xD0; -MIDIEvent.PITCHBEND = 0xE0; -MIDIEvent.TIMETICK = 0xF8; - -MIDIEvent.commands = { - 0x80: "note off", - 0x90: "note on", - 0xA0: "key pressure", - 0xB0: "controller change", - 0xC0: "program change", - 0xD0: "channel pressure", - 0xE0: "pitch bend", - 0xF0: "system", - 0xF2: "Song pos", - 0xF3: "Song select", - 0xF6: "Tune request", - 0xF8: "time tick", - 0xFA: "Start Song", - 0xFB: "Continue Song", - 0xFC: "Stop Song", - 0xFE: "Sensing", - 0xFF: "Reset" -} - -//MIDI wrapper -function MIDIInterface( on_ready, on_error ) -{ - if(!navigator.requestMIDIAccess) - { - this.error = "not suppoorted"; - if(on_error) - on_error("Not supported"); - else - console.error("MIDI NOT SUPPORTED, enable by chrome://flags"); - return; - } - - this.on_ready = on_ready; - - this.state = { - note: [], - cc: [] - }; - - - - navigator.requestMIDIAccess().then( this.onMIDISuccess.bind(this), this.onMIDIFailure.bind(this) ); -} - -MIDIInterface.input = null; - -MIDIInterface.MIDIEvent = MIDIEvent; - -MIDIInterface.prototype.onMIDISuccess = function(midiAccess) -{ - console.log( "MIDI ready!" ); - console.log( midiAccess ); - this.midi = midiAccess; // store in the global (in real usage, would probably keep in an object instance) - this.updatePorts(); - - if (this.on_ready) - this.on_ready(this); -} - -MIDIInterface.prototype.updatePorts = function() -{ - var midi = this.midi; - this.input_ports = midi.inputs; - var num = 0; - - var it = this.input_ports.values(); - var it_value = it.next(); - while( it_value && it_value.done === false ) - { - var port_info = it_value.value; - console.log( "Input port [type:'" + port_info.type + "'] id:'" + port_info.id + - "' manufacturer:'" + port_info.manufacturer + "' name:'" + port_info.name + - "' version:'" + port_info.version + "'" ); - num++; - it_value = it.next(); - } - this.num_input_ports = num; - - num = 0; - this.output_ports = midi.outputs; - var it = this.output_ports.values(); - var it_value = it.next(); - while( it_value && it_value.done === false ) - { - var port_info = it_value.value; - console.log( "Output port [type:'" + port_info.type + "'] id:'" + port_info.id + - "' manufacturer:'" + port_info.manufacturer + "' name:'" + port_info.name + - "' version:'" + port_info.version + "'" ); - num++; - it_value = it.next(); - } - this.num_output_ports = num; - - - /* OLD WAY - for (var i = 0; i < this.input_ports.size; ++i) { - var input = this.input_ports.get(i); - if(!input) - continue; //sometimes it is null?! - console.log( "Input port [type:'" + input.type + "'] id:'" + input.id + - "' manufacturer:'" + input.manufacturer + "' name:'" + input.name + - "' version:'" + input.version + "'" ); - num++; - } - this.num_input_ports = num; - - - num = 0; - this.output_ports = midi.outputs; - for (var i = 0; i < this.output_ports.size; ++i) { - var output = this.output_ports.get(i); - if(!output) - continue; - console.log( "Output port [type:'" + output.type + "'] id:'" + output.id + - "' manufacturer:'" + output.manufacturer + "' name:'" + output.name + - "' version:'" + output.version + "'" ); - num++; - } - this.num_output_ports = num; - */ -} - -MIDIInterface.prototype.onMIDIFailure = function(msg) -{ - console.error( "Failed to get MIDI access - " + msg ); -} - -MIDIInterface.prototype.openInputPort = function( port, callback ) -{ - var input_port = this.input_ports.get( "input-" + port ); - if(!input_port) - return false; - MIDIInterface.input = this; - var that = this; - - input_port.onmidimessage = function(a) { - var midi_event = new MIDIEvent(a.data); - that.updateState( midi_event ); - if(callback) - callback(a.data, midi_event ); - if(MIDIInterface.on_message) - MIDIInterface.on_message( a.data, midi_event ); - } - console.log("port open: ", input_port); - return true; -} - -MIDIInterface.parseMsg = function(data) -{ - -} - -MIDIInterface.prototype.updateState = function( midi_event ) -{ - switch( midi_event.cmd ) - { - case MIDIEvent.NOTEON: this.state.note[ midi_event.value1|0 ] = midi_event.value2; break; - case MIDIEvent.NOTEOFF: this.state.note[ midi_event.value1|0 ] = 0; break; - case MIDIEvent.CONTROLLERCHANGE: this.state.cc[ midi_event.getCC() ] = midi_event.getCCValue(); break; - } -} - -MIDIInterface.prototype.sendMIDI = function( port, midi_data ) -{ - if( !midi_data ) - return; - - var output_port = this.output_ports.get( "output-" + port ); - if(!output_port) - return; - - MIDIInterface.output = this; - - if( midi_data.constructor === MIDIEvent) - output_port.send( midi_data.data ); - else - output_port.send( midi_data ); -} - - - -function LGMIDIIn() -{ - this.addOutput( "on_midi", LiteGraph.EVENT ); - this.addOutput( "out", "midi" ); - this.properties = {port: 0}; - this._last_midi_event = null; - this._current_midi_event = null; - - var that = this; - new MIDIInterface( function( midi ){ - //open - that._midi = midi; - if(that._waiting) - that.onStart(); - that._waiting = false; - }); -} - -LGMIDIIn.MIDIInterface = MIDIInterface; - -LGMIDIIn.title = "MIDI Input"; -LGMIDIIn.desc = "Reads MIDI from a input port"; - -LGMIDIIn.prototype.getPropertyInfo = function(name) -{ - if(!this._midi) - return; - - if(name == "port") - { - var values = {}; - for (var i = 0; i < this._midi.input_ports.size; ++i) - { - var input = this._midi.input_ports.get( "input-" + i); - values[i] = i + ".- " + input.name + " version:" + input.version; - } - return { type: "enum", values: values }; - } -} - -LGMIDIIn.prototype.onStart = function() -{ - if(this._midi) - this._midi.openInputPort( this.properties.port, this.onMIDIEvent.bind(this) ); - else - this._waiting = true; -} - -LGMIDIIn.prototype.onMIDIEvent = function( data, midi_event ) -{ - this._last_midi_event = midi_event; - - this.trigger( "on_midi", midi_event ); - if(midi_event.cmd == MIDIEvent.NOTEON) - this.trigger( "on_noteon", midi_event ); - else if(midi_event.cmd == MIDIEvent.NOTEOFF) - this.trigger( "on_noteoff", midi_event ); - else if(midi_event.cmd == MIDIEvent.CONTROLLERCHANGE) - this.trigger( "on_cc", midi_event ); - else if(midi_event.cmd == MIDIEvent.PROGRAMCHANGE) - this.trigger( "on_pc", midi_event ); - else if(midi_event.cmd == MIDIEvent.PITCHBEND) - this.trigger( "on_pitchbend", midi_event ); -} - -LGMIDIIn.prototype.onExecute = function() -{ - if(this.outputs) - { - var last = this._last_midi_event; - for(var i = 0; i < this.outputs.length; ++i) - { - var output = this.outputs[i]; - var v = null; - switch (output.name) - { - case "midi": v = this._midi; break; - case "last_midi": v = last; break; - default: - continue; - } - this.setOutputData( i, v ); - } - } -} - -LGMIDIIn.prototype.onGetOutputs = function() { - return [ - ["last_midi","midi"], - ["on_midi",LiteGraph.EVENT], - ["on_noteon",LiteGraph.EVENT], - ["on_noteoff",LiteGraph.EVENT], - ["on_cc",LiteGraph.EVENT], - ["on_pc",LiteGraph.EVENT], - ["on_pitchbend",LiteGraph.EVENT] - ]; -} - -LiteGraph.registerNodeType("midi/input", LGMIDIIn); - - -function LGMIDIOut() -{ - this.addInput( "send", LiteGraph.EVENT ); - this.properties = {port: 0}; - - var that = this; - new MIDIInterface( function( midi ){ - that._midi = midi; - }); -} - -LGMIDIOut.MIDIInterface = MIDIInterface; - -LGMIDIOut.title = "MIDI Output"; -LGMIDIOut.desc = "Sends MIDI to output channel"; - -LGMIDIOut.prototype.getPropertyInfo = function(name) -{ - if(!this._midi) - return; - - if(name == "port") - { - var values = {}; - for (var i = 0; i < this._midi.output_ports.size; ++i) - { - var output = this._midi.output_ports.get(i); - values[i] = i + ".- " + output.name + " version:" + output.version; - } - return { type: "enum", values: values }; - } -} - - -LGMIDIOut.prototype.onAction = function(event, midi_event ) -{ - console.log(midi_event); - if(!this._midi) - return; - if(event == "send") - this._midi.sendMIDI( this.port, midi_event ); - this.trigger("midi",midi_event); -} - -LGMIDIOut.prototype.onGetInputs = function() { - return [["send",LiteGraph.ACTION]]; -} - -LGMIDIOut.prototype.onGetOutputs = function() { - return [["on_midi",LiteGraph.EVENT]]; -} - -LiteGraph.registerNodeType("midi/output", LGMIDIOut); - - -function LGMIDIShow() -{ - this.addInput( "on_midi", LiteGraph.EVENT ); - this._str = ""; - this.size = [200,40] -} - -LGMIDIShow.title = "MIDI Show"; -LGMIDIShow.desc = "Shows MIDI in the graph"; - -LGMIDIShow.prototype.onAction = function(event, midi_event ) -{ - if(!midi_event) - return; - if(midi_event.constructor === MIDIEvent) - this._str = midi_event.toString(); - else - this._str = "???"; -} - -LGMIDIShow.prototype.onDrawForeground = function( ctx ) -{ - if( !this._str ) - return; - - ctx.font = "30px Arial"; - ctx.fillText( this._str, 10, this.size[1] * 0.8 ); -} - -LGMIDIShow.prototype.onGetInputs = function() { - return [["in",LiteGraph.ACTION]]; -} - -LGMIDIShow.prototype.onGetOutputs = function() { - return [["on_midi",LiteGraph.EVENT]]; -} - -LiteGraph.registerNodeType("midi/show", LGMIDIShow); - - - -function LGMIDIFilter() -{ - this.properties = { - channel: -1, - cmd: -1, - min_value: -1, - max_value: -1 - }; - - this.addInput( "in", LiteGraph.EVENT ); - this.addOutput( "on_midi", LiteGraph.EVENT ); -} - -LGMIDIFilter.title = "MIDI Filter"; -LGMIDIFilter.desc = "Filters MIDI messages"; - -LGMIDIFilter.prototype.onAction = function(event, midi_event ) -{ - if(!midi_event || midi_event.constructor !== MIDIEvent) - return; - - if( this.properties.channel != -1 && midi_event.channel != this.properties.channel) - return; - if(this.properties.cmd != -1 && midi_event.cmd != this.properties.cmd) - return; - if(this.properties.min_value != -1 && midi_event.data[1] < this.properties.min_value) - return; - if(this.properties.max_value != -1 && midi_event.data[1] > this.properties.max_value) - return; - this.trigger("on_midi",midi_event); -} - -LiteGraph.registerNodeType("midi/filter", LGMIDIFilter); - - -function LGMIDIEvent() -{ - this.properties = { - channel: 0, - cmd: "CC", - value1: 1, - value2: 1 - }; - - this.addInput( "send", LiteGraph.EVENT ); - this.addInput( "assign", LiteGraph.EVENT ); - this.addOutput( "on_midi", LiteGraph.EVENT ); -} - -LGMIDIEvent.title = "MIDIEvent"; -LGMIDIEvent.desc = "Create a MIDI Event"; - -LGMIDIEvent.prototype.onAction = function( event, midi_event ) -{ - if(event == "assign") - { - this.properties.channel = midi_event.channel; - this.properties.cmd = midi_event.cmd; - this.properties.value1 = midi_event.data[1]; - this.properties.value2 = midi_event.data[2]; - return; - } - - //send - var midi_event = new MIDIEvent(); - midi_event.channel = this.properties.channel; - if(this.properties.cmd && this.properties.cmd.constructor === String) - midi_event.setCommandFromString( this.properties.cmd ); - else - midi_event.cmd = this.properties.cmd; - midi_event.data[0] = midi_event.cmd | midi_event.channel; - midi_event.data[1] = Number(this.properties.value1); - midi_event.data[2] = Number(this.properties.value2); - this.trigger("on_midi",midi_event); -} - -LGMIDIEvent.prototype.onExecute = function() -{ - var props = this.properties; - - if(this.outputs) - { - for(var i = 0; i < this.outputs.length; ++i) - { - var output = this.outputs[i]; - var v = null; - switch (output.name) - { - case "midi": - v = new MIDIEvent(); - v.setup([ props.cmd, props.value1, props.value2 ]); - v.channel = props.channel; - break; - case "command": v = props.cmd; break; - case "cc": v = props.value1; break; - case "cc_value": v = props.value2; break; - case "note": v = (props.cmd == MIDIEvent.NOTEON || props.cmd == MIDIEvent.NOTEOFF) ? props.value1 : null; break; - case "velocity": v = props.cmd == MIDIEvent.NOTEON ? props.value2 : null; break; - case "pitch": v = props.cmd == MIDIEvent.NOTEON ? MIDIEvent.computePitch( props.value1 ) : null; break; - case "pitchbend": v = props.cmd == MIDIEvent.PITCHBEND ? MIDIEvent.computePitchBend( props.value1, props.value2 ) : null; break; - default: - continue; - } - if(v !== null) - this.setOutputData( i, v ); - } - } -} - -LGMIDIEvent.prototype.onPropertyChanged = function(name,value) -{ - if(name == "cmd") - this.properties.cmd = MIDIEvent.computeCommandFromString( value ); -} - - -LGMIDIEvent.prototype.onGetOutputs = function() { - return [ - ["midi","midi"], - ["on_midi",LiteGraph.EVENT], - ["command","number"], - ["note","number"], - ["velocity","number"], - ["cc","number"], - ["cc_value","number"], - ["pitch","number"], - ["pitchbend","number"] - ]; -} - - -LiteGraph.registerNodeType("midi/event", LGMIDIEvent); - - -function LGMIDICC() -{ - this.properties = { -// channel: 0, - cc: 1, - value: 0 - }; - - this.addOutput( "value", "number" ); -} - -LGMIDICC.title = "MIDICC"; -LGMIDICC.desc = "gets a Controller Change"; - -LGMIDICC.prototype.onExecute = function() -{ - var props = this.properties; - if( MIDIInterface.input ) - this.properties.value = MIDIInterface.input.state.cc[ this.properties.cc ]; - this.setOutputData( 0, this.properties.value ); -} - -LiteGraph.registerNodeType("midi/cc", LGMIDICC); - - - - -function now() { return window.performance.now() } - -})( this ); -(function( global ) -{ -var LiteGraph = global.LiteGraph; - -var LGAudio = {}; -global.LGAudio = LGAudio; - -LGAudio.getAudioContext = function() -{ - if(!this._audio_context) - { - window.AudioContext = window.AudioContext || window.webkitAudioContext; - if(!window.AudioContext) - { - console.error("AudioContext not supported by browser"); - return null; - } - this._audio_context = new AudioContext(); - this._audio_context.onmessage = function(msg) { console.log("msg",msg);}; - this._audio_context.onended = function(msg) { console.log("ended",msg);}; - this._audio_context.oncomplete = function(msg) { console.log("complete",msg);}; - } - - //in case it crashes - //if(this._audio_context.state == "suspended") - // this._audio_context.resume(); - return this._audio_context; -} - -LGAudio.connect = function( audionodeA, audionodeB ) -{ - try - { - audionodeA.connect( audionodeB ); - } - catch (err) - { - console.warn("LGraphAudio:",err); - } -} - -LGAudio.disconnect = function( audionodeA, audionodeB ) -{ - try - { - audionodeA.disconnect( audionodeB ); - } - catch (err) - { - console.warn("LGraphAudio:",err); - } -} - -LGAudio.changeAllAudiosConnections = function( node, connect ) -{ - if(node.inputs) - { - for(var i = 0; i < node.inputs.length; ++i) - { - var input = node.inputs[i]; - var link_info = node.graph.links[ input.link ]; - if(!link_info) - continue; - - var origin_node = node.graph.getNodeById( link_info.origin_id ); - var origin_audionode = null; - if( origin_node.getAudioNodeInOutputSlot ) - origin_audionode = origin_node.getAudioNodeInOutputSlot( link_info.origin_slot ); - else - origin_audionode = origin_node.audionode; - - var target_audionode = null; - if( node.getAudioNodeInInputSlot ) - target_audionode = node.getAudioNodeInInputSlot( i ); - else - target_audionode = node.audionode; - - if(connect) - LGAudio.connect( origin_audionode, target_audionode ); - else - LGAudio.disconnect( origin_audionode, target_audionode ); - } - } - - if(node.outputs) - { - for(var i = 0; i < node.outputs.length; ++i) - { - var output = node.outputs[i]; - for(var j = 0; j < output.links.length; ++j) - { - var link_info = node.graph.links[ output.links[j] ]; - if(!link_info) - continue; - - var origin_audionode = null; - if( node.getAudioNodeInOutputSlot ) - origin_audionode = node.getAudioNodeInOutputSlot( i ); - else - origin_audionode = node.audionode; - - var target_node = node.graph.getNodeById( link_info.target_id ); - var target_audionode = null; - if( target_node.getAudioNodeInInputSlot ) - target_audionode = target_node.getAudioNodeInInputSlot( link_info.target_slot ); - else - target_audionode = target_node.audionode; - - if(connect) - LGAudio.connect( origin_audionode, target_audionode ); - else - LGAudio.disconnect( origin_audionode, target_audionode ); - } - } - } -} - -//used by many nodes -LGAudio.onConnectionsChange = function( connection, slot, connected, link_info ) -{ - //only process the outputs events - if(connection != LiteGraph.OUTPUT) - return; - - var target_node = null; - if( link_info ) - target_node = this.graph.getNodeById( link_info.target_id ); - - if( !target_node ) - return; - - //get origin audionode - var local_audionode = null; - if(this.getAudioNodeInOutputSlot) - local_audionode = this.getAudioNodeInOutputSlot( slot ); - else - local_audionode = this.audionode; - - //get target audionode - var target_audionode = null; - if(target_node.getAudioNodeInInputSlot) - target_audionode = target_node.getAudioNodeInInputSlot( link_info.target_slot ); - else - target_audionode = target_node.audionode; - - //do the connection/disconnection - if( connected ) - LGAudio.connect( local_audionode, target_audionode ); - else - LGAudio.disconnect( local_audionode, target_audionode ); -} - -//this function helps creating wrappers to existing classes -LGAudio.createAudioNodeWrapper = function( class_object ) -{ - var old_func = class_object.prototype.onPropertyChanged; - - class_object.prototype.onPropertyChanged = function(name, value) - { - if(old_func) - old_func.call(this,name,value); - - if(!this.audionode) - return; - - if( this.audionode[ name ] === undefined ) - return; - - if( this.audionode[ name ].value !== undefined ) - this.audionode[ name ].value = value; - else - this.audionode[ name ] = value; - } - - class_object.prototype.onConnectionsChange = LGAudio.onConnectionsChange; -} - -//contains the samples decoded of the loaded audios in AudioBuffer format -LGAudio.cached_audios = {}; - -LGAudio.loadSound = function( url, on_complete, on_error ) -{ - if( LGAudio.cached_audios[ url ] && url.indexOf("blob:") == -1 ) - { - if(on_complete) - on_complete( LGAudio.cached_audios[ url ] ); - return; - } - - if( LGAudio.onProcessAudioURL ) - url = LGAudio.onProcessAudioURL( url ); - - //load new sample - var request = new XMLHttpRequest(); - request.open('GET', url, true); - request.responseType = 'arraybuffer'; - - var context = LGAudio.getAudioContext(); - - // Decode asynchronously - request.onload = function() { - console.log("AudioSource loaded"); - context.decodeAudioData( request.response, function( buffer ) { - console.log("AudioSource decoded"); - LGAudio.cached_audios[ url ] = buffer; - if(on_complete) - on_complete( buffer ); - }, onError); - } - request.send(); - - function onError(err) - { - console.log("Audio loading sample error:",err); - if(on_error) - on_error(err); - } - - return request; -} - - -//**************************************************** - -function LGAudioSource() -{ - this.properties = { - src: "", - gain: 0.5, - loop: true, - autoplay: true, - playbackRate: 1 - }; - - this._loading_audio = false; - this._audiobuffer = null; //points to AudioBuffer with the audio samples decoded - this._audionodes = []; - this._last_sourcenode = null; //the last AudioBufferSourceNode (there could be more if there are several sounds playing) - - this.addOutput( "out", "audio" ); - this.addInput( "gain", "number" ); - - //init context - var context = LGAudio.getAudioContext(); - - //create gain node to control volume - this.audionode = context.createGain(); - this.audionode.graphnode = this; - this.audionode.gain.value = this.properties.gain; - - //debug - if(this.properties.src) - this.loadSound( this.properties.src ); -} - -LGAudioSource["@src"] = { widget: "resource" }; -LGAudioSource.supported_extensions = ["wav","ogg","mp3"]; - - -LGAudioSource.prototype.onAdded = function(graph) -{ - if(graph.status === LGraph.STATUS_RUNNING) - this.onStart(); -} - -LGAudioSource.prototype.onStart = function() -{ - if(!this._audiobuffer) - return; - - if(this.properties.autoplay) - this.playBuffer( this._audiobuffer ); -} - -LGAudioSource.prototype.onStop = function() -{ - this.stopAllSounds(); -} - -LGAudioSource.prototype.onPause = function() -{ - this.pauseAllSounds(); -} - -LGAudioSource.prototype.onUnpause = function() -{ - this.unpauseAllSounds(); - //this.onStart(); -} - - -LGAudioSource.prototype.onRemoved = function() -{ - this.stopAllSounds(); - if(this._dropped_url) - URL.revokeObjectURL( this._url ); -} - -LGAudioSource.prototype.stopAllSounds = function() -{ - //iterate and stop - for(var i = 0; i < this._audionodes.length; ++i ) - { - if(this._audionodes[i].started) - { - this._audionodes[i].started = false; - this._audionodes[i].stop(); - } - //this._audionodes[i].disconnect( this.audionode ); - } - this._audionodes.length = 0; -} - -LGAudioSource.prototype.pauseAllSounds = function() -{ - LGAudio.getAudioContext().suspend(); -} - -LGAudioSource.prototype.unpauseAllSounds = function() -{ - LGAudio.getAudioContext().resume(); -} - -LGAudioSource.prototype.onExecute = function() -{ - if(this.inputs) - for(var i = 0; i < this.inputs.length; ++i) - { - var input = this.inputs[i]; - if(input.link == null) - continue; - var v = this.getInputData(i); - if( v === undefined ) - continue; - if( input.name == "gain" ) - this.audionode.gain.value = v; - else if( input.name == "playbackRate" ) - { - this.properties.playbackRate = v; - for(var j = 0; j < this._audionodes.length; ++j) - this._audionodes[j].playbackRate.value = v; - } - } - - if(this.outputs) - for(var i = 0; i < this.outputs.length; ++i) - { - var output = this.outputs[i]; - if( output.name == "buffer" && this._audiobuffer ) - this.setOutputData( i, this._audiobuffer ); - } -} - -LGAudioSource.prototype.onAction = function(event) -{ - if(this._audiobuffer) - { - if(event == "Play") - this.playBuffer(this._audiobuffer); - else if(event == "Stop") - this.stopAllSounds(); - } -} - -LGAudioSource.prototype.onPropertyChanged = function( name, value ) -{ - if( name == "src" ) - this.loadSound( value ); - else if(name == "gain") - this.audionode.gain.value = value; - else if(name == "playbackRate") - { - for(var j = 0; j < this._audionodes.length; ++j) - this._audionodes[j].playbackRate.value = value; - } -} - -LGAudioSource.prototype.playBuffer = function( buffer ) -{ - var that = this; - var context = LGAudio.getAudioContext(); - - //create a new audionode (this is mandatory, AudioAPI doesnt like to reuse old ones) - var audionode = context.createBufferSource(); //create a AudioBufferSourceNode - this._last_sourcenode = audionode; - audionode.graphnode = this; - audionode.buffer = buffer; - audionode.loop = this.properties.loop; - audionode.playbackRate.value = this.properties.playbackRate; - this._audionodes.push( audionode ); - audionode.connect( this.audionode ); //connect to gain - this._audionodes.push( audionode ); - - audionode.onended = function() - { - //console.log("ended!"); - that.trigger("ended"); - //remove - var index = that._audionodes.indexOf( audionode ); - if(index != -1) - that._audionodes.splice(index,1); - } - - if(!audionode.started) - { - audionode.started = true; - audionode.start(); - } - return audionode; -} - -LGAudioSource.prototype.loadSound = function( url ) -{ - var that = this; - - //kill previous load - if(this._request) - { - this._request.abort(); - this._request = null; - } - - this._audiobuffer = null; //points to the audiobuffer once the audio is loaded - this._loading_audio = false; - - if(!url) - return; - - this._request = LGAudio.loadSound( url, inner ); - - this._loading_audio = true; - this.boxcolor = "#AA4"; - - function inner( buffer ) - { - this.boxcolor = LiteGraph.NODE_DEFAULT_BOXCOLOR; - that._audiobuffer = buffer; - that._loading_audio = false; - //if is playing, then play it - if(that.graph && that.graph.status === LGraph.STATUS_RUNNING) - that.onStart(); //this controls the autoplay already - } -} - -//Helps connect/disconnect AudioNodes when new connections are made in the node -LGAudioSource.prototype.onConnectionsChange = LGAudio.onConnectionsChange; - -LGAudioSource.prototype.onGetInputs = function() -{ - return [["playbackRate","number"],["Play",LiteGraph.ACTION],["Stop",LiteGraph.ACTION]]; -} - -LGAudioSource.prototype.onGetOutputs = function() -{ - return [["buffer","audiobuffer"],["ended",LiteGraph.EVENT]]; -} - -LGAudioSource.prototype.onDropFile = function(file) -{ - if(this._dropped_url) - URL.revokeObjectURL( this._dropped_url ); - var url = URL.createObjectURL( file ); - this.properties.src = url; - this.loadSound( url ); - this._dropped_url = url; -} - - -LGAudioSource.title = "Source"; -LGAudioSource.desc = "Plays audio"; -LiteGraph.registerNodeType("audio/source", LGAudioSource); - - -//***************************************************** - -function LGAudioAnalyser() -{ - this.properties = { - fftSize: 2048, - minDecibels: -100, - maxDecibels: -10, - smoothingTimeConstant: 0.5 - }; - - var context = LGAudio.getAudioContext(); - - this.audionode = context.createAnalyser(); - this.audionode.graphnode = this; - this.audionode.fftSize = this.properties.fftSize; - this.audionode.minDecibels = this.properties.minDecibels; - this.audionode.maxDecibels = this.properties.maxDecibels; - this.audionode.smoothingTimeConstant = this.properties.smoothingTimeConstant; - - this.addInput("in","audio"); - this.addOutput("freqs","array"); - this.addOutput("samples","array"); - - this._freq_bin = null; - this._time_bin = null; -} - -LGAudioAnalyser.prototype.onPropertyChanged = function(name, value) -{ - this.audionode[ name ] = value; -} - -LGAudioAnalyser.prototype.onExecute = function() -{ - if(this.isOutputConnected(0)) - { - //send FFT - var bufferLength = this.audionode.frequencyBinCount; - if( !this._freq_bin || this._freq_bin.length != bufferLength ) - this._freq_bin = new Uint8Array( bufferLength ); - this.audionode.getByteFrequencyData( this._freq_bin ); - this.setOutputData(0,this._freq_bin); - } - - //send analyzer - if(this.isOutputConnected(1)) - { - //send Samples - var bufferLength = this.audionode.frequencyBinCount; - if( !this._time_bin || this._time_bin.length != bufferLength ) - this._time_bin = new Uint8Array( bufferLength ); - this.audionode.getByteTimeDomainData( this._time_bin ); - this.setOutputData(1,this._time_bin); - } - - - //properties - for(var i = 1; i < this.inputs.length; ++i) - { - var input = this.inputs[i]; - if(input.link == null) - continue; - var v = this.getInputData(i); - if (v !== undefined) - this.audionode[ input.name ].value = v; - } - - - - //time domain - //this.audionode.getFloatTimeDomainData( dataArray ); -} - -LGAudioAnalyser.prototype.onGetInputs = function() -{ - return [["minDecibels","number"],["maxDecibels","number"],["smoothingTimeConstant","number"]]; -} - -LGAudioAnalyser.prototype.onGetOutputs = function() -{ - return [["freqs","array"],["samples","array"]]; -} - - -LGAudioAnalyser.title = "Analyser"; -LGAudioAnalyser.desc = "Audio Analyser"; -LiteGraph.registerNodeType( "audio/analyser", LGAudioAnalyser ); - -//***************************************************** - -function LGAudioGain() -{ - //default - this.properties = { - gain: 1 - }; - - this.audionode = LGAudio.getAudioContext().createGain(); - this.addInput("in","audio"); - this.addInput("gain","number"); - this.addOutput("out","audio"); -} - -LGAudioGain.prototype.onExecute = function() -{ - if(!this.inputs || !this.inputs.length) - return; - - for(var i = 1; i < this.inputs.length; ++i) - { - var input = this.inputs[i]; - var v = this.getInputData(i); - if(v !== undefined) - this.audionode[ input.name ].value = v; - } -} - -LGAudio.createAudioNodeWrapper( LGAudioGain ); - -LGAudioGain.title = "Gain"; -LGAudioGain.desc = "Audio gain"; -LiteGraph.registerNodeType("audio/gain", LGAudioGain); - - -function LGAudioConvolver() -{ - //default - this.properties = { - impulse_src:"", - normalize: true - }; - - this.audionode = LGAudio.getAudioContext().createConvolver(); - this.addInput("in","audio"); - this.addOutput("out","audio"); -} - -LGAudio.createAudioNodeWrapper( LGAudioConvolver ); - -LGAudioConvolver.prototype.onRemove = function() -{ - if(this._dropped_url) - URL.revokeObjectURL( this._dropped_url ); -} - -LGAudioConvolver.prototype.onPropertyChanged = function( name, value ) -{ - if( name == "impulse_src" ) - this.loadImpulse( value ); - else if( name == "normalize" ) - this.audionode.normalize = value; -} - -LGAudioConvolver.prototype.onDropFile = function(file) -{ - if(this._dropped_url) - URL.revokeObjectURL( this._dropped_url ); - this._dropped_url = URL.createObjectURL( file ); - this.properties.impulse_src = this._dropped_url; - this.loadImpulse( this._dropped_url ); -} - -LGAudioConvolver.prototype.loadImpulse = function( url ) -{ - var that = this; - - //kill previous load - if(this._request) - { - this._request.abort(); - this._request = null; - } - - this._impulse_buffer = null; - this._loading_impulse = false; - - if(!url) - return; - - //load new sample - this._request = LGAudio.loadSound( url, inner ); - this._loading_impulse = true; - - // Decode asynchronously - function inner( buffer ) { - that._impulse_buffer = buffer; - that.audionode.buffer = buffer; - console.log("Impulse signal set"); - that._loading_impulse = false; - } -} - -LGAudioConvolver.title = "Convolver"; -LGAudioConvolver.desc = "Convolves the signal (used for reverb)"; -LiteGraph.registerNodeType("audio/convolver", LGAudioConvolver); - - -function LGAudioDynamicsCompressor() -{ - //default - this.properties = { - threshold: -50, - knee: 40, - ratio: 12, - reduction: -20, - attack: 0, - release: 0.25 - }; - - this.audionode = LGAudio.getAudioContext().createDynamicsCompressor(); - this.addInput("in","audio"); - this.addOutput("out","audio"); -} - -LGAudio.createAudioNodeWrapper( LGAudioDynamicsCompressor ); - -LGAudioDynamicsCompressor.prototype.onExecute = function() -{ - if(!this.inputs || !this.inputs.length) - return; - for(var i = 1; i < this.inputs.length; ++i) - { - var input = this.inputs[i]; - if(input.link == null) - continue; - var v = this.getInputData(i); - if(v !== undefined) - this.audionode[ input.name ].value = v; - } -} - -LGAudioDynamicsCompressor.prototype.onGetInputs = function() -{ - return [["threshold","number"],["knee","number"],["ratio","number"],["reduction","number"],["attack","number"],["release","number"]]; -} - -LGAudioDynamicsCompressor.title = "DynamicsCompressor"; -LGAudioDynamicsCompressor.desc = "Dynamics Compressor"; -LiteGraph.registerNodeType("audio/dynamicsCompressor", LGAudioDynamicsCompressor); - - -function LGAudioWaveShaper() -{ - //default - this.properties = { - }; - - this.audionode = LGAudio.getAudioContext().createWaveShaper(); - this.addInput("in","audio"); - this.addInput("shape","waveshape"); - this.addOutput("out","audio"); -} - -LGAudioWaveShaper.prototype.onExecute = function() -{ - if(!this.inputs || !this.inputs.length) - return; - var v = this.getInputData(1); - if(v === undefined) - return; - this.audionode.curve = v; -} - -LGAudioWaveShaper.prototype.setWaveShape = function(shape) -{ - this.audionode.curve = shape; -} - -LGAudio.createAudioNodeWrapper( LGAudioWaveShaper ); - -/* disabled till I dont find a way to do a wave shape -LGAudioWaveShaper.title = "WaveShaper"; -LGAudioWaveShaper.desc = "Distortion using wave shape"; -LiteGraph.registerNodeType("audio/waveShaper", LGAudioWaveShaper); -*/ - -function LGAudioMixer() -{ - //default - this.properties = { - gain1: 0.5, - gain2: 0.5 - }; - - this.audionode = LGAudio.getAudioContext().createGain(); - - this.audionode1 = LGAudio.getAudioContext().createGain(); - this.audionode1.gain.value = this.properties.gain1; - this.audionode2 = LGAudio.getAudioContext().createGain(); - this.audionode2.gain.value = this.properties.gain2; - - this.audionode1.connect( this.audionode ); - this.audionode2.connect( this.audionode ); - - this.addInput("in1","audio"); - this.addInput("in1 gain","number"); - this.addInput("in2","audio"); - this.addInput("in2 gain","number"); - - this.addOutput("out","audio"); -} - -LGAudioMixer.prototype.getAudioNodeInInputSlot = function( slot ) -{ - if(slot == 0) - return this.audionode1; - else if(slot == 2) - return this.audionode2; -} - -LGAudioMixer.prototype.onPropertyChanged = function( name, value ) -{ - if( name == "gain1" ) - this.audionode1.gain.value = value; - else if( name == "gain2" ) - this.audionode2.gain.value = value; -} - - -LGAudioMixer.prototype.onExecute = function() -{ - if(!this.inputs || !this.inputs.length) - return; - - for(var i = 1; i < this.inputs.length; ++i) - { - var input = this.inputs[i]; - - if(input.link == null || input.type == "audio") - continue; - - var v = this.getInputData(i); - if(v === undefined) - continue; - - if(i == 1) - this.audionode1.gain.value = v; - else if(i == 3) - this.audionode2.gain.value = v; - } -} - -LGAudio.createAudioNodeWrapper( LGAudioMixer ); - -LGAudioMixer.title = "Mixer"; -LGAudioMixer.desc = "Audio mixer"; -LiteGraph.registerNodeType("audio/mixer", LGAudioMixer); - - -function LGAudioDelay() -{ - //default - this.properties = { - delayTime: 0.5 - }; - - this.audionode = LGAudio.getAudioContext().createDelay( 10 ); - this.audionode.delayTime.value = this.properties.delayTime; - this.addInput("in","audio"); - this.addInput("time","number"); - this.addOutput("out","audio"); -} - -LGAudio.createAudioNodeWrapper( LGAudioDelay ); - -LGAudioDelay.prototype.onExecute = function() -{ - var v = this.getInputData(1); - if(v !== undefined ) - this.audionode.delayTime.value = v; -} - -LGAudioDelay.title = "Delay"; -LGAudioDelay.desc = "Audio delay"; -LiteGraph.registerNodeType("audio/delay", LGAudioDelay); - - -function LGAudioBiquadFilter() -{ - //default - this.properties = { - frequency: 350, - detune: 0, - Q: 1 - }; - this.addProperty("type","lowpass","enum",{values:["lowpass","highpass","bandpass","lowshelf","highshelf","peaking","notch","allpass"]}); - - //create node - this.audionode = LGAudio.getAudioContext().createBiquadFilter(); - - //slots - this.addInput("in","audio"); - this.addOutput("out","audio"); -} - -LGAudioBiquadFilter.prototype.onExecute = function() -{ - if(!this.inputs || !this.inputs.length) - return; - - for(var i = 1; i < this.inputs.length; ++i) - { - var input = this.inputs[i]; - if(input.link == null) - continue; - var v = this.getInputData(i); - if(v !== undefined) - this.audionode[ input.name ].value = v; - } -} - -LGAudioBiquadFilter.prototype.onGetInputs = function() -{ - return [["frequency","number"],["detune","number"],["Q","number"]]; -} - -LGAudio.createAudioNodeWrapper( LGAudioBiquadFilter ); - -LGAudioBiquadFilter.title = "BiquadFilter"; -LGAudioBiquadFilter.desc = "Audio filter"; -LiteGraph.registerNodeType("audio/biquadfilter", LGAudioBiquadFilter); - - - - -function LGAudioOscillatorNode() -{ - //default - this.properties = { - frequency: 440, - detune: 0, - type: "sine" - }; - this.addProperty("type","sine","enum",{values:["sine","square","sawtooth","triangle","custom"]}); - - //create node - this.audionode = LGAudio.getAudioContext().createOscillator(); - - //slots - this.addOutput("out","audio"); -} - -LGAudioOscillatorNode.prototype.onStart = function() -{ - if(!this.audionode.started) - { - this.audionode.started = true; - this.audionode.start(); - } -} - -LGAudioOscillatorNode.prototype.onStop = function() -{ - if(this.audionode.started) - { - this.audionode.started = false; - this.audionode.stop(); - } -} - -LGAudioOscillatorNode.prototype.onPause = function() -{ - this.onStop(); -} - -LGAudioOscillatorNode.prototype.onUnpause = function() -{ - this.onStart(); -} - -LGAudioOscillatorNode.prototype.onExecute = function() -{ - if(!this.inputs || !this.inputs.length) - return; - - for(var i = 0; i < this.inputs.length; ++i) - { - var input = this.inputs[i]; - if(input.link == null) - continue; - var v = this.getInputData(i); - if(v !== undefined) - this.audionode[ input.name ].value = v; - } -} - -LGAudioOscillatorNode.prototype.onGetInputs = function() -{ - return [["frequency","number"],["detune","number"],["type","string"]]; -} - -LGAudio.createAudioNodeWrapper( LGAudioOscillatorNode ); - -LGAudioOscillatorNode.title = "Oscillator"; -LGAudioOscillatorNode.desc = "Oscillator"; -LiteGraph.registerNodeType("audio/oscillator", LGAudioOscillatorNode); - - -//***************************************************** - -//EXTRA - - -function LGAudioVisualization() -{ - this.properties = { - continuous: true, - mark: -1 - }; - - this.addInput("data","array"); - this.addInput("mark","number"); - this.size = [300,200]; - this._last_buffer = null; -} - -LGAudioVisualization.prototype.onExecute = function() -{ - this._last_buffer = this.getInputData(0); - var v = this.getInputData(1); - if(v !== undefined) - this.properties.mark = v; - this.setDirtyCanvas(true,false); -} - -LGAudioVisualization.prototype.onDrawForeground = function(ctx) -{ - if(!this._last_buffer) - return; - - var buffer = this._last_buffer; - - //delta represents how many samples we advance per pixel - var delta = buffer.length / this.size[0]; - var h = this.size[1]; - - ctx.fillStyle = "black"; - ctx.fillRect(0,0,this.size[0],this.size[1]); - ctx.strokeStyle = "white"; - ctx.beginPath(); - var x = 0; - - if(this.properties.continuous) - { - ctx.moveTo(x,h); - for(var i = 0; i < buffer.length; i+= delta) - { - ctx.lineTo(x,h - (buffer[i|0]/255) * h); - x++; - } - } - else - { - for(var i = 0; i < buffer.length; i+= delta) - { - ctx.moveTo(x+0.5,h); - ctx.lineTo(x+0.5,h - (buffer[i|0]/255) * h); - x++; - } - } - ctx.stroke(); - - if(this.properties.mark >= 0) - { - var samplerate = LGAudio.getAudioContext().sampleRate; - var binfreq = samplerate / buffer.length; - var x = 2 * (this.properties.mark / binfreq) / delta; - if(x >= this.size[0]) - x = this.size[0]-1; - ctx.strokeStyle = "red"; - ctx.beginPath(); - ctx.moveTo(x,h); - ctx.lineTo(x,0); - ctx.stroke(); - } -} - -LGAudioVisualization.title = "Visualization"; -LGAudioVisualization.desc = "Audio Visualization"; -LiteGraph.registerNodeType("audio/visualization", LGAudioVisualization); - - -function LGAudioBandSignal() -{ - //default - this.properties = { - band: 440, - amplitude: 1 - }; - - this.addInput("freqs","array"); - this.addOutput("signal","number"); -} - -LGAudioBandSignal.prototype.onExecute = function() -{ - this._freqs = this.getInputData(0); - if( !this._freqs ) - return; - - var band = this.properties.band; - var v = this.getInputData(1); - if(v !== undefined) - band = v; - - var samplerate = LGAudio.getAudioContext().sampleRate; - var binfreq = samplerate / this._freqs.length; - var index = 2 * (band / binfreq); - var v = 0; - if( index < 0 ) - v = this._freqs[ 0 ]; - if( index >= this._freqs.length ) - v = this._freqs[ this._freqs.length - 1]; - else - { - var pos = index|0; - var v0 = this._freqs[ pos ]; - var v1 = this._freqs[ pos+1 ]; - var f = index - pos; - v = v0 * (1-f) + v1 * f; - } - - this.setOutputData( 0, (v/255) * this.properties.amplitude ); -} - -LGAudioBandSignal.prototype.onGetInputs = function() -{ - return [["band","number"]]; -} - -LGAudioBandSignal.title = "Signal"; -LGAudioBandSignal.desc = "extract the signal of some frequency"; -LiteGraph.registerNodeType("audio/signal", LGAudioBandSignal); - - -function LGAudioScript() -{ - if(!LGAudioScript.default_code) - { - var code = LGAudioScript.default_function.toString(); - var index = code.indexOf("{")+1; - var index2 = code.lastIndexOf("}"); - LGAudioScript.default_code = code.substr(index, index2 - index); - } - - //default - this.properties = { - code: LGAudioScript.default_code - }; - - //create node - var ctx = LGAudio.getAudioContext(); - if(ctx.createScriptProcessor) - this.audionode = ctx.createScriptProcessor(4096,1,1); //buffer size, input channels, output channels - else - { - console.warn("ScriptProcessorNode deprecated"); - this.audionode = ctx.createGain(); //bypass audio - } - - this.processCode(); - if(!LGAudioScript._bypass_function) - LGAudioScript._bypass_function = this.audionode.onaudioprocess; - - //slots - this.addInput("in","audio"); - this.addOutput("out","audio"); -} - -LGAudioScript.prototype.onAdded = function( graph ) -{ - if(graph.status == LGraph.STATUS_RUNNING) - this.audionode.onaudioprocess = this._callback; -} - -LGAudioScript["@code"] = { widget: "code" }; - -LGAudioScript.prototype.onStart = function() -{ - this.audionode.onaudioprocess = this._callback; -} - -LGAudioScript.prototype.onStop = function() -{ - this.audionode.onaudioprocess = LGAudioScript._bypass_function; -} - -LGAudioScript.prototype.onPause = function() -{ - this.audionode.onaudioprocess = LGAudioScript._bypass_function; -} - -LGAudioScript.prototype.onUnpause = function() -{ - this.audionode.onaudioprocess = this._callback; -} - -LGAudioScript.prototype.onExecute = function() -{ - //nothing! because we need an onExecute to receive onStart... fix that -} - -LGAudioScript.prototype.onRemoved = function() -{ - this.audionode.onaudioprocess = LGAudioScript._bypass_function; -} - -LGAudioScript.prototype.processCode = function() -{ - try - { - var func = new Function( "properties", this.properties.code ); - this._script = new func( this.properties ); - this._old_code = this.properties.code; - this._callback = this._script.onaudioprocess; - } - catch (err) - { - console.error("Error in onaudioprocess code",err); - this._callback = LGAudioScript._bypass_function; - this.audionode.onaudioprocess = this._callback; - } -} - -LGAudioScript.prototype.onPropertyChanged = function( name, value ) -{ - if(name == "code") - { - this.properties.code = value; - this.processCode(); - if(this.graph && this.graph.status == LGraph.STATUS_RUNNING) - this.audionode.onaudioprocess = this._callback; - } -} - -LGAudioScript.default_function = function() -{ - -this.onaudioprocess = function(audioProcessingEvent) { - // The input buffer is the song we loaded earlier - var inputBuffer = audioProcessingEvent.inputBuffer; - - // The output buffer contains the samples that will be modified and played - var outputBuffer = audioProcessingEvent.outputBuffer; - - // Loop through the output channels (in this case there is only one) - for (var channel = 0; channel < outputBuffer.numberOfChannels; channel++) { - var inputData = inputBuffer.getChannelData(channel); - var outputData = outputBuffer.getChannelData(channel); - - // Loop through the 4096 samples - for (var sample = 0; sample < inputBuffer.length; sample++) { - // make output equal to the same as the input - outputData[sample] = inputData[sample]; - } - } -} - -} - -LGAudio.createAudioNodeWrapper( LGAudioScript ); - -LGAudioScript.title = "Script"; -LGAudioScript.desc = "apply script to signal"; -LiteGraph.registerNodeType("audio/script", LGAudioScript); - - -function LGAudioDestination() -{ - this.audionode = LGAudio.getAudioContext().destination; - this.addInput("in","audio"); -} - - -LGAudioDestination.title = "Destination"; -LGAudioDestination.desc = "Audio output"; -LiteGraph.registerNodeType("audio/destination", LGAudioDestination); - - - - -})( this ); -//event related nodes -(function(global){ -var LiteGraph = global.LiteGraph; - -function LGWebSocket() -{ - this.size = [60,20]; - this.addInput("send", LiteGraph.ACTION); - this.addOutput("received", LiteGraph.EVENT); - this.addInput("in", 0 ); - this.addOutput("out", 0 ); - this.properties = { - url: "", - room: "lgraph" //allows to filter messages - }; - this._ws = null; - this._last_data = []; -} - -LGWebSocket.title = "WebSocket"; -LGWebSocket.desc = "Send data through a websocket"; - -LGWebSocket.prototype.onPropertyChanged = function(name,value) -{ - if(name == "url") - this.createSocket(); -} - -LGWebSocket.prototype.onExecute = function() -{ - if(!this._ws && this.properties.url) - this.createSocket(); - - if(!this._ws || this._ws.readyState != WebSocket.OPEN ) - return; - - var room = this.properties.room; - - for(var i = 1; i < this.inputs.length; ++i) - { - var data = this.getInputData(i); - if(data != null) - { - var json; - try - { - json = JSON.stringify({ type: 0, room: room, channel: i, data: data }); - } - catch (err) - { - continue; - } - this._ws.send( json ); - } - } - - for(var i = 1; i < this.outputs.length; ++i) - this.setOutputData( i, this._last_data[i] ); -} - -LGWebSocket.prototype.createSocket = function() -{ - var that = this; - var url = this.properties.url; - if( url.substr(0,2) != "ws" ) - url = "ws://" + url; - this._ws = new WebSocket( url ); - this._ws.onopen = function() - { - console.log("ready"); - that.boxcolor = "#8E8"; - } - this._ws.onmessage = function(e) - { - var data = JSON.parse( e.data ); - if( data.room && data.room != this.properties.room ) - return; - if( e.data.type == 1 ) - that.triggerSlot( 0, data ); - else - that._last_data[ e.data.channel || 0 ] = data.data; - } - this._ws.onerror = function(e) - { - console.log("couldnt connect to websocket"); - that.boxcolor = "#E88"; - } - this._ws.onclose = function(e) - { - console.log("connection closed"); - that.boxcolor = "#000"; - } -} - -LGWebSocket.prototype.send = function(data) -{ - if(!this._ws || this._ws.readyState != WebSocket.OPEN ) - return; - this._ws.send( JSON.stringify({ type:1, msg: data }) ); -} - -LGWebSocket.prototype.onAction = function( action, param ) -{ - if(!this._ws || this._ws.readyState != WebSocket.OPEN ) - return; - this._ws.send( { type: 1, room: this.properties.room, action: action, data: param } ); -} - -LGWebSocket.prototype.onGetInputs = function() -{ - return [["in",0]]; -} - -LGWebSocket.prototype.onGetOutputs = function() -{ - return [["out",0]]; -} - -LiteGraph.registerNodeType("network/websocket", LGWebSocket ); - - -//It is like a websocket but using the SillyServer.js server that bounces packets back to all clients connected: -//For more information: https://github.com/jagenjo/SillyServer.js - -function LGSillyClient() -{ - this.size = [60,20]; - this.addInput("send", LiteGraph.ACTION); - this.addOutput("received", LiteGraph.EVENT); - this.addInput("in", 0 ); - this.addOutput("out", 0 ); - this.properties = { - url: "tamats.com:55000", - room: "lgraph", - save_bandwidth: true - }; - - this._server = null; - this.createSocket(); - this._last_input_data = []; - this._last_output_data = []; -} - -LGSillyClient.title = "SillyClient"; -LGSillyClient.desc = "Connects to SillyServer to broadcast messages"; - -LGSillyClient.prototype.onPropertyChanged = function(name,value) -{ - var final_url = (this.properties.url + "/" + this.properties.room); - if(this._server && this._final_url != final_url ) - { - this._server.connect( this.properties.url, this.properties.room ); - this._final_url = final_url; - } -} - -LGSillyClient.prototype.onExecute = function() -{ - if(!this._server || !this._server.is_connected) - return; - - var save_bandwidth = this.properties.save_bandwidth; - - for(var i = 1; i < this.inputs.length; ++i) - { - var data = this.getInputData(i); - if(data != null) - { - if( save_bandwidth && this._last_input_data[i] == data ) - continue; - this._server.sendMessage( { type: 0, channel: i, data: data } ); - this._last_input_data[i] = data; - } - } - - for(var i = 1; i < this.outputs.length; ++i) - this.setOutputData( i, this._last_output_data[i] ); -} - -LGSillyClient.prototype.createSocket = function() -{ - var that = this; - if(typeof(SillyClient) == "undefined") - { - if(!this._error) - console.error("SillyClient node cannot be used, you must include SillyServer.js"); - this._error = true; - return; - } - - this._server = new SillyClient(); - this._server.on_ready = function() - { - console.log("ready"); - that.boxcolor = "#8E8"; - } - this._server.on_message = function(id,msg) - { - var data = null; - try - { - data = JSON.parse( msg ); - } - catch (err) - { - return; - } - - if(data.type == 1) - that.triggerSlot( 0, data ); - else - that._last_output_data[ data.channel || 0 ] = data.data; - } - this._server.on_error = function(e) - { - console.log("couldnt connect to websocket"); - that.boxcolor = "#E88"; - } - this._server.on_close = function(e) - { - console.log("connection closed"); - that.boxcolor = "#000"; - } - - if(this.properties.url && this.properties.room) - { - this._server.connect( this.properties.url, this.properties.room ); - this._final_url = (this.properties.url + "/" + this.properties.room); - } -} - -LGSillyClient.prototype.send = function(data) -{ - if(!this._server || !this._server.is_connected) - return; - this._server.sendMessage( { type:1, data: data } ); -} - -LGSillyClient.prototype.onAction = function( action, param ) -{ - if(!this._server || !this._server.is_connected) - return; - this._server.sendMessage( { type: 1, action: action, data: param } ); -} - -LGSillyClient.prototype.onGetInputs = function() -{ - return [["in",0]]; -} - -LGSillyClient.prototype.onGetOutputs = function() -{ - return [["out",0]]; -} - -LiteGraph.registerNodeType("network/sillyclient", LGSillyClient ); - - +(function(global){ +// ************************************************************* +// LiteGraph CLASS ******* +// ************************************************************* + +/* FYI: links are stored in graph.links with this structure per object +{ + id: number + type: string, + origin_id: number, + origin_slot: number, + target_id: number, + target_slot: number, + data: * +}; +*/ + +/** +* The Global Scope. It contains all the registered node classes. +* +* @class LiteGraph +* @constructor +*/ + +var LiteGraph = global.LiteGraph = { + + CANVAS_GRID_SIZE: 10, + + NODE_TITLE_HEIGHT: 20, + NODE_SLOT_HEIGHT: 15, + NODE_WIDGET_HEIGHT: 20, + NODE_WIDTH: 140, + NODE_MIN_WIDTH: 50, + NODE_COLLAPSED_RADIUS: 10, + NODE_COLLAPSED_WIDTH: 80, + NODE_TITLE_COLOR: "#999", + NODE_TEXT_SIZE: 14, + NODE_TEXT_COLOR: "#AAA", + NODE_SUBTEXT_SIZE: 12, + NODE_DEFAULT_COLOR: "#333", + NODE_DEFAULT_BGCOLOR: "#444", + NODE_DEFAULT_BOXCOLOR: "#888", + NODE_DEFAULT_SHAPE: "box", + + LINK_COLOR: "#AAD", + EVENT_LINK_COLOR: "#F85", + CONNECTING_LINK_COLOR: "#AFA", + + MAX_NUMBER_OF_NODES: 1000, //avoid infinite loops + DEFAULT_POSITION: [100,100],//default node position + VALID_SHAPES: ["default","box","round","card"], //,"circle" + + //shapes are used for nodes but also for slots + BOX_SHAPE: 1, + ROUND_SHAPE: 2, + CIRCLE_SHAPE: 3, + CARD_SHAPE: 4, + ARROW_SHAPE: 5, + + //enums + INPUT: 1, + OUTPUT: 2, + + EVENT: -1, //for outputs + ACTION: -1, //for inputs + + ALWAYS: 0, + ON_EVENT: 1, + NEVER: 2, + ON_TRIGGER: 3, + + UP: 1, + DOWN:2, + LEFT:3, + RIGHT:4, + CENTER:5, + + NORMAL_TITLE: 0, + NO_TITLE: 1, + TRANSPARENT_TITLE: 2, + AUTOHIDE_TITLE: 3, + + proxy: null, //used to redirect calls + node_images_path: "", + + debug: false, + throw_errors: true, + allow_scripts: true, + registered_node_types: {}, //nodetypes by string + node_types_by_file_extension: {}, //used for droping files in the canvas + Nodes: {}, //node types by classname + + /** + * Register a node class so it can be listed when the user wants to create a new one + * @method registerNodeType + * @param {String} type name of the node and path + * @param {Class} base_class class containing the structure of a node + */ + + registerNodeType: function(type, base_class) + { + if(!base_class.prototype) + throw("Cannot register a simple object, it must be a class with a prototype"); + base_class.type = type; + + if(LiteGraph.debug) + console.log("Node registered: " + type); + + var categories = type.split("/"); + var classname = base_class.name; + + var pos = type.lastIndexOf("/"); + base_class.category = type.substr(0,pos); + + if(!base_class.title) + base_class.title = classname; + //info.name = name.substr(pos+1,name.length - pos); + + //extend class + if(base_class.prototype) //is a class + for(var i in LGraphNode.prototype) + if(!base_class.prototype[i]) + base_class.prototype[i] = LGraphNode.prototype[i]; + + Object.defineProperty( base_class.prototype, "shape",{ + set: function(v) { + switch(v) + { + case "default": delete this._shape; break; + case "box": this._shape = LiteGraph.BOX_SHAPE; break; + case "round": this._shape = LiteGraph.ROUND_SHAPE; break; + case "circle": this._shape = LiteGraph.CIRCLE_SHAPE; break; + case "card": this._shape = LiteGraph.CARD_SHAPE; break; + default: + this._shape = v; + } + }, + get: function(v) + { + return this._shape; + }, + enumerable: true + }); + + this.registered_node_types[ type ] = base_class; + if(base_class.constructor.name) + this.Nodes[ classname ] = base_class; + + //warnings + if(base_class.prototype.onPropertyChange) + console.warn("LiteGraph node class " + type + " has onPropertyChange method, it must be called onPropertyChanged with d at the end"); + + if( base_class.supported_extensions ) + { + for(var i in base_class.supported_extensions ) + this.node_types_by_file_extension[ base_class.supported_extensions[i].toLowerCase() ] = base_class; + } + }, + + /** + * Create a new node type by passing a function, it wraps it with a propper class and generates inputs according to the parameters of the function. + * Useful to wrap simple methods that do not require properties, and that only process some input to generate an output. + * @method wrapFunctionAsNode + * @param {String} name node name with namespace (p.e.: 'math/sum') + * @param {Function} func + * @param {Array} param_types [optional] an array containing the type of every parameter, otherwise parameters will accept any type + * @param {String} return_type [optional] string with the return type, otherwise it will be generic + */ + wrapFunctionAsNode: function( name, func, param_types, return_type ) + { + var params = Array(func.length); + var code = ""; + var names = LiteGraph.getParameterNames( func ); + for(var i = 0; i < names.length; ++i) + code += "this.addInput('"+names[i]+"',"+(param_types && param_types[i] ? "'" + param_types[i] + "'" : "0") + ");\n"; + code += "this.addOutput('out',"+( return_type ? "'" + return_type + "'" : 0 )+");\n"; + var classobj = Function(code); + classobj.title = name.split("/").pop(); + classobj.desc = "Generated from " + func.name; + classobj.prototype.onExecute = function onExecute() + { + for(var i = 0; i < params.length; ++i) + params[i] = this.getInputData(i); + var r = func.apply( this, params ); + this.setOutputData(0,r); + } + this.registerNodeType( name, classobj ); + }, + + /** + * Adds this method to all nodetypes, existing and to be created + * (You can add it to LGraphNode.prototype but then existing node types wont have it) + * @method addNodeMethod + * @param {Function} func + */ + addNodeMethod: function( name, func ) + { + LGraphNode.prototype[name] = func; + for(var i in this.registered_node_types) + { + var type = this.registered_node_types[i]; + if(type.prototype[name]) + type.prototype["_" + name] = type.prototype[name]; //keep old in case of replacing + type.prototype[name] = func; + } + }, + + /** + * Create a node of a given type with a name. The node is not attached to any graph yet. + * @method createNode + * @param {String} type full name of the node class. p.e. "math/sin" + * @param {String} name a name to distinguish from other nodes + * @param {Object} options to set options + */ + + createNode: function( type, title, options ) + { + var base_class = this.registered_node_types[type]; + if (!base_class) + { + if(LiteGraph.debug) + console.log("GraphNode type \"" + type + "\" not registered."); + return null; + } + + var prototype = base_class.prototype || base_class; + + title = title || base_class.title || type; + + var node = new base_class( title ); + node.type = type; + + if(!node.title && title) node.title = title; + if(!node.properties) node.properties = {}; + if(!node.properties_info) node.properties_info = []; + if(!node.flags) node.flags = {}; + if(!node.size) node.size = node.computeSize(); + if(!node.pos) node.pos = LiteGraph.DEFAULT_POSITION.concat(); + if(!node.mode) node.mode = LiteGraph.ALWAYS; + + //extra options + if(options) + { + for(var i in options) + node[i] = options[i]; + } + + return node; + }, + + /** + * Returns a registered node type with a given name + * @method getNodeType + * @param {String} type full name of the node class. p.e. "math/sin" + * @return {Class} the node class + */ + + getNodeType: function(type) + { + return this.registered_node_types[type]; + }, + + + /** + * Returns a list of node types matching one category + * @method getNodeType + * @param {String} category category name + * @return {Array} array with all the node classes + */ + + getNodeTypesInCategory: function( category, filter ) + { + var r = []; + for(var i in this.registered_node_types) + { + var type = this.registered_node_types[i]; + if(filter && type.filter && type.filter != filter) + continue; + + if(category == "" ) + { + if (type.category == null) + r.push(type); + } + else if (type.category == category) + r.push(type); + } + + return r; + }, + + /** + * Returns a list with all the node type categories + * @method getNodeTypesCategories + * @return {Array} array with all the names of the categories + */ + + getNodeTypesCategories: function() + { + var categories = {"":1}; + for(var i in this.registered_node_types) + if(this.registered_node_types[i].category && !this.registered_node_types[i].skip_list) + categories[ this.registered_node_types[i].category ] = 1; + var result = []; + for(var i in categories) + result.push(i); + return result; + }, + + //debug purposes: reloads all the js scripts that matches a wilcard + reloadNodes: function (folder_wildcard) + { + var tmp = document.getElementsByTagName("script"); + //weird, this array changes by its own, so we use a copy + var script_files = []; + for(var i in tmp) + script_files.push(tmp[i]); + + + var docHeadObj = document.getElementsByTagName("head")[0]; + folder_wildcard = document.location.href + folder_wildcard; + + for(var i in script_files) + { + var src = script_files[i].src; + if( !src || src.substr(0,folder_wildcard.length ) != folder_wildcard) + continue; + + try + { + if(LiteGraph.debug) + console.log("Reloading: " + src); + var dynamicScript = document.createElement("script"); + dynamicScript.type = "text/javascript"; + dynamicScript.src = src; + docHeadObj.appendChild(dynamicScript); + docHeadObj.removeChild(script_files[i]); + } + catch (err) + { + if(LiteGraph.throw_errors) + throw err; + if(LiteGraph.debug) + console.log("Error while reloading " + src); + } + } + + if(LiteGraph.debug) + console.log("Nodes reloaded"); + }, + + //separated just to improve if it doesnt work + cloneObject: function(obj, target) + { + if(obj == null) return null; + var r = JSON.parse( JSON.stringify( obj ) ); + if(!target) return r; + + for(var i in r) + target[i] = r[i]; + return target; + }, + + isValidConnection: function( type_a, type_b ) + { + if( !type_a || //generic output + !type_b || //generic input + type_a == type_b || //same type (is valid for triggers) + type_a == LiteGraph.EVENT && type_b == LiteGraph.ACTION ) + return true; + + // Enforce string type to handle toLowerCase call (-1 number not ok) + type_a = String(type_a); + type_b = String(type_b); + type_a = type_a.toLowerCase(); + type_b = type_b.toLowerCase(); + + // For nodes supporting multiple connection types + if( type_a.indexOf(",") == -1 && type_b.indexOf(",") == -1 ) + return type_a == type_b; + + // Check all permutations to see if one is valid + var supported_types_a = type_a.split(","); + var supported_types_b = type_b.split(","); + for(var i = 0; i < supported_types_a.length; ++i) + for(var j = 0; j < supported_types_b.length; ++j) + if( supported_types_a[i] == supported_types_b[j] ) + return true; + + return false; + } +}; + +//timer that works everywhere +if(typeof(performance) != "undefined") + LiteGraph.getTime = performance.now.bind(performance); +else if(typeof(Date) != "undefined" && Date.now) + LiteGraph.getTime = Date.now.bind(Date); +else if(typeof(process) != "undefined") + LiteGraph.getTime = function(){ + var t = process.hrtime(); + return t[0]*0.001 + t[1]*(1e-6); + } +else + LiteGraph.getTime = function getTime() { return (new Date).getTime(); } + + + + + + +//********************************************************************************* +// LGraph CLASS +//********************************************************************************* + +/** +* 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. +* +* @class LGraph +* @constructor +* @param {Object} o data from previous serialization [optional] +*/ + +function LGraph( o ) +{ + if (LiteGraph.debug) + console.log("Graph created"); + this.list_of_graphcanvas = null; + this.clear(); + + if(o) + this.configure(o); +} + +global.LGraph = LiteGraph.LGraph = LGraph; + +//default supported types +LGraph.supported_types = ["number","string","boolean"]; + +//used to know which types of connections support this graph (some graphs do not allow certain types) +LGraph.prototype.getSupportedTypes = function() { return this.supported_types || LGraph.supported_types; } + +LGraph.STATUS_STOPPED = 1; +LGraph.STATUS_RUNNING = 2; + +/** +* Removes all nodes from this graph +* @method clear +*/ + +LGraph.prototype.clear = function() +{ + this.stop(); + this.status = LGraph.STATUS_STOPPED; + + this.last_node_id = 1; + this.last_link_id = 1; + + this._version = -1; //used to detect changes + + //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 + + //other scene stuff + this._groups = []; + + //links + this.links = {}; //container with all the links + + //iterations + this.iteration = 0; + + //custom data + this.config = {}; + + //timing + this.globaltime = 0; + this.runningtime = 0; + this.fixedtime = 0; + this.fixedtime_lapse = 0.01; + this.elapsed_time = 0.01; + this.last_update_time = 0; + this.starttime = 0; + + this.catch_errors = true; + + //subgraph_data + this.global_inputs = {}; + this.global_outputs = {}; + + //notify canvas to redraw + this.change(); + + this.sendActionToCanvas("clear"); +} + +/** +* Attach Canvas to this graph +* @method attachCanvas +* @param {GraphCanvas} graph_canvas +*/ + +LGraph.prototype.attachCanvas = function(graphcanvas) +{ + if(graphcanvas.constructor != LGraphCanvas) + throw("attachCanvas expects a LGraphCanvas instance"); + if(graphcanvas.graph && graphcanvas.graph != this) + graphcanvas.graph.detachCanvas( graphcanvas ); + + graphcanvas.graph = this; + if(!this.list_of_graphcanvas) + this.list_of_graphcanvas = []; + this.list_of_graphcanvas.push(graphcanvas); +} + +/** +* Detach Canvas from this graph +* @method detachCanvas +* @param {GraphCanvas} graph_canvas +*/ +LGraph.prototype.detachCanvas = function(graphcanvas) +{ + if(!this.list_of_graphcanvas) + return; + + var pos = this.list_of_graphcanvas.indexOf( graphcanvas ); + if(pos == -1) + return; + graphcanvas.graph = null; + this.list_of_graphcanvas.splice(pos,1); +} + +/** +* Starts running this graph every interval milliseconds. +* @method start +* @param {number} interval amount of milliseconds between executions, default is 1 +*/ + +LGraph.prototype.start = function(interval) +{ + if( this.status == LGraph.STATUS_RUNNING ) + return; + this.status = LGraph.STATUS_RUNNING; + + if(this.onPlayEvent) + this.onPlayEvent(); + + this.sendEventToAllNodes("onStart"); + + //launch + this.starttime = LiteGraph.getTime(); + this.last_update_time = this.starttime; + interval = interval || 1; + var that = this; + + this.execution_timer_id = setInterval( function() { + //execute + that.runStep(1, !this.catch_errors ); + },interval); +} + +/** +* Stops the execution loop of the graph +* @method stop execution +*/ + +LGraph.prototype.stop = function() +{ + if( this.status == LGraph.STATUS_STOPPED ) + return; + + this.status = LGraph.STATUS_STOPPED; + + if(this.onStopEvent) + this.onStopEvent(); + + if(this.execution_timer_id != null) + clearInterval(this.execution_timer_id); + this.execution_timer_id = null; + + this.sendEventToAllNodes("onStop"); +} + +/** +* Run N steps (cycles) of the graph +* @method runStep +* @param {number} num number of steps to run, default is 1 +*/ + +LGraph.prototype.runStep = function( num, do_not_catch_errors ) +{ + num = num || 1; + + var start = LiteGraph.getTime(); + this.globaltime = 0.001 * (start - this.starttime); + + var nodes = this._nodes_executable ? this._nodes_executable : this._nodes; + if(!nodes) + return; + + if( do_not_catch_errors ) + { + //iterations + for(var i = 0; i < num; i++) + { + for( var j = 0, l = nodes.length; j < l; ++j ) + { + var node = nodes[j]; + if( node.mode == LiteGraph.ALWAYS && node.onExecute ) + node.onExecute(); + } + + this.fixedtime += this.fixedtime_lapse; + if( this.onExecuteStep ) + this.onExecuteStep(); + } + + if( this.onAfterExecute ) + this.onAfterExecute(); + } + else + { + try + { + //iterations + for(var i = 0; i < num; i++) + { + for( var j = 0, l = nodes.length; j < l; ++j ) + { + var node = nodes[j]; + if( node.mode == LiteGraph.ALWAYS && node.onExecute ) + node.onExecute(); + } + + this.fixedtime += this.fixedtime_lapse; + if( this.onExecuteStep ) + this.onExecuteStep(); + } + + if( this.onAfterExecute ) + this.onAfterExecute(); + this.errors_in_execution = false; + } + catch (err) + { + this.errors_in_execution = true; + if(LiteGraph.throw_errors) + throw err; + if(LiteGraph.debug) + console.log("Error during execution: " + err); + this.stop(); + } + } + + var now = LiteGraph.getTime(); + var elapsed = now - start; + if (elapsed == 0) + elapsed = 1; + this.execution_time = 0.001 * elapsed; + this.globaltime += 0.001 * elapsed; + this.iteration += 1; + this.elapsed_time = (now - this.last_update_time) * 0.001; + this.last_update_time = now; +} + +/** +* Updates the graph execution order according to relevance of the nodes (nodes with only outputs have more relevance than +* nodes with only inputs. +* @method updateExecutionOrder +*/ +LGraph.prototype.updateExecutionOrder = function() +{ + this._nodes_in_order = this.computeExecutionOrder( false ); + this._nodes_executable = []; + for(var i = 0; i < this._nodes_in_order.length; ++i) + if( this._nodes_in_order[i].onExecute ) + this._nodes_executable.push( this._nodes_in_order[i] ); +} + +//This is more internal, it computes the order and returns it +LGraph.prototype.computeExecutionOrder = function( only_onExecute, set_level ) +{ + var L = []; + var S = []; + var M = {}; + var visited_links = {}; //to avoid repeating links + var remaining_links = {}; //to a + + //search for the nodes without inputs (starting nodes) + for (var i = 0, l = this._nodes.length; i < l; ++i) + { + var node = this._nodes[i]; + if( only_onExecute && !node.onExecute ) + continue; + + M[node.id] = node; //add to pending nodes + + var num = 0; //num of input connections + if(node.inputs) + for(var j = 0, l2 = node.inputs.length; j < l2; j++) + if(node.inputs[j] && node.inputs[j].link != null) + num += 1; + + if(num == 0) //is a starting node + { + S.push(node); + if(set_level) + node._level = 1; + } + else //num of input links + { + if(set_level) + node._level = 0; + remaining_links[node.id] = num; + } + } + + while(true) + { + if(S.length == 0) + break; + + //get an starting node + var node = S.shift(); + L.push(node); //add to ordered list + delete M[node.id]; //remove from the pending nodes + + if(!node.outputs) + continue; + + //for every output + for(var i = 0; i < node.outputs.length; i++) + { + var output = node.outputs[i]; + //not connected + if(output == null || output.links == null || output.links.length == 0) + continue; + + //for every connection + for(var j = 0; j < output.links.length; j++) + { + var link_id = output.links[j]; + var link = this.links[link_id]; + if(!link) + continue; + + //already visited link (ignore it) + if(visited_links[ link.id ]) + continue; + + var target_node = this.getNodeById( link.target_id ); + if(target_node == null) + { + visited_links[ link.id ] = true; + continue; + } + + if(set_level && (!target_node._level || target_node._level <= node._level)) + target_node._level = node._level + 1; + + visited_links[link.id] = true; //mark as visited + remaining_links[target_node.id] -= 1; //reduce the number of links remaining + if (remaining_links[ target_node.id ] == 0) + S.push(target_node); //if no more links, then add to starters array + } + } + } + + //the remaining ones (loops) + for(var i in M) + L.push( M[i] ); + + 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; ++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; +} + +/** +* Returns all the nodes that could affect this one (ancestors) by crawling all the inputs recursively. +* It doesnt include the node itself +* @method getAncestors +* @return {Array} an array with all the LGraphNodes that affect this node, in order of execution +*/ +LGraph.prototype.getAncestors = function( node ) +{ + var ancestors = []; + var pending = [node]; + var visited = {}; + + while (pending.length) + { + var current = pending.shift(); + if(!current.inputs) + continue; + if( !visited[ current.id ] && current != node ) + { + visited[ current.id ] = true; + ancestors.push( current ); + } + + for(var i = 0; i < current.inputs.length;++i) + { + var input = current.getInputNode(i); + if( input && ancestors.indexOf( input ) == -1) + { + pending.push( input ); + } + } + } + + ancestors.sort(function(a,b){ return a.order - b.order;}); + return ancestors; +} + +/** +* Positions every node in a more readable manner +* @method arrange +*/ +LGraph.prototype.arrange = function( margin ) +{ + margin = margin || 40; + + var nodes = this.computeExecutionOrder( false, true ); + var columns = []; + for(var i = 0; i < nodes.length; ++i) + { + var node = nodes[i]; + var col = node._level || 1; + if(!columns[col]) + columns[col] = []; + columns[col].push( node ); + } + + var x = margin; + + for(var i = 0; i < columns.length; ++i) + { + var column = columns[i]; + if(!column) + continue; + var max_size = 100; + var y = margin; + for(var j = 0; j < column.length; ++j) + { + var node = column[j]; + node.pos[0] = x; + node.pos[1] = y; + if(node.size[0] > max_size) + max_size = node.size[0]; + y += node.size[1] + margin; + } + x += max_size + margin; + } + + this.setDirtyCanvas(true,true); +} + + +/** +* Returns the amount of time the graph has been running in milliseconds +* @method getTime +* @return {number} number of milliseconds the graph has been running +*/ +LGraph.prototype.getTime = function() +{ + return this.globaltime; +} + +/** +* Returns the amount of time accumulated using the fixedtime_lapse var. This is used in context where the time increments should be constant +* @method getFixedTime +* @return {number} number of milliseconds the graph has been running +*/ + +LGraph.prototype.getFixedTime = function() +{ + return this.fixedtime; +} + +/** +* Returns the amount of time it took to compute the latest iteration. Take into account that this number could be not correct +* if the nodes are using graphical actions +* @method getElapsedTime +* @return {number} number of milliseconds it took the last cycle +*/ + +LGraph.prototype.getElapsedTime = function() +{ + return this.elapsed_time; +} + +/** +* Sends an event to all the nodes, useful to trigger stuff +* @method sendEventToAllNodes +* @param {String} eventname the name of the event (function to be called) +* @param {Array} params parameters in array format +*/ + +LGraph.prototype.sendEventToAllNodes = function( eventname, params, mode ) +{ + mode = mode || LiteGraph.ALWAYS; + + var nodes = this._nodes_in_order ? this._nodes_in_order : this._nodes; + if(!nodes) + return; + + for( var j = 0, l = nodes.length; j < l; ++j ) + { + var node = nodes[j]; + if(node[eventname] && node.mode == mode ) + { + if(params === undefined) + node[eventname](); + else if(params && params.constructor === Array) + node[eventname].apply( node, params ); + else + node[eventname](params); + } + } +} + +LGraph.prototype.sendActionToCanvas = function(action, params) +{ + if(!this.list_of_graphcanvas) + return; + + for(var i = 0; i < this.list_of_graphcanvas.length; ++i) + { + var c = this.list_of_graphcanvas[i]; + if( c[action] ) + c[action].apply(c, params); + } +} + +/** +* Adds a new node instasnce to this graph +* @method add +* @param {LGraphNode} node the instance of the node +*/ + +LGraph.prototype.add = function( node, skip_compute_order) +{ + if(!node) + return; + + //groups + if( node.constructor === LGraphGroup ) + { + this._groups.push( node ); + this.setDirtyCanvas(true); + this.change(); + node.graph = this; + this._version++; + return; + } + + //nodes + if(node.id != -1 && this._nodes_by_id[node.id] != null) + { + console.warn("LiteGraph: there is already a node with this ID, changing it"); + node.id = ++this.last_node_id; + } + + if(this._nodes.length >= LiteGraph.MAX_NUMBER_OF_NODES) + throw("LiteGraph: max number of nodes in a graph reached"); + + //give him an id + if(node.id == null || node.id == -1) + node.id = ++this.last_node_id; + else if (this.last_node_id < node.id) + this.last_node_id = node.id; + + + node.graph = this; + this._version++; + + this._nodes.push(node); + this._nodes_by_id[node.id] = node; + + if(node.onAdded) + node.onAdded( this ); + + if(this.config.align_to_grid) + node.alignToGrid(); + + if(!skip_compute_order) + this.updateExecutionOrder(); + + if(this.onNodeAdded) + this.onNodeAdded(node); + + + this.setDirtyCanvas(true); + this.change(); + + return node; //to chain actions +} + +/** +* Removes a node from the graph +* @method remove +* @param {LGraphNode} node the instance of the node +*/ + +LGraph.prototype.remove = function(node) +{ + if(node.constructor === LiteGraph.LGraphGroup) + { + var index = this._groups.indexOf(node); + if(index != -1) + this._groups.splice(index,1); + node.graph = null; + this._version++; + this.setDirtyCanvas(true,true); + this.change(); + return; + } + + if(this._nodes_by_id[node.id] == null) + return; //not found + + if(node.ignore_remove) + return; //cannot be removed + + //disconnect inputs + if(node.inputs) + for(var i = 0; i < node.inputs.length; i++) + { + var slot = node.inputs[i]; + if(slot.link != null) + node.disconnectInput(i); + } + + //disconnect outputs + if(node.outputs) + for(var i = 0; i < node.outputs.length; i++) + { + var slot = node.outputs[i]; + if(slot.links != null && slot.links.length) + node.disconnectOutput(i); + } + + //node.id = -1; //why? + + //callback + if(node.onRemoved) + node.onRemoved(); + + node.graph = null; + this._version++; + + //remove from canvas render + if(this.list_of_graphcanvas) + { + for(var i = 0; i < this.list_of_graphcanvas.length; ++i) + { + var canvas = this.list_of_graphcanvas[i]; + if(canvas.selected_nodes[node.id]) + delete canvas.selected_nodes[node.id]; + if(canvas.node_dragged == node) + canvas.node_dragged = null; + } + } + + //remove from containers + var pos = this._nodes.indexOf(node); + if(pos != -1) + this._nodes.splice(pos,1); + delete this._nodes_by_id[node.id]; + + if(this.onNodeRemoved) + this.onNodeRemoved(node); + + this.setDirtyCanvas(true,true); + this.change(); + + this.updateExecutionOrder(); +} + +/** +* Returns a node by its id. +* @method getNodeById +* @param {Number} id +*/ + +LGraph.prototype.getNodeById = function( id ) +{ + if( id == null ) + return null; + return this._nodes_by_id[ id ]; +} + +/** +* Returns a list of nodes that matches a class +* @method findNodesByClass +* @param {Class} classObject the class itself (not an string) +* @return {Array} a list with all the nodes of this type +*/ + +LGraph.prototype.findNodesByClass = function(classObject) +{ + var r = []; + for(var i = 0, l = this._nodes.length; i < l; ++i) + if(this._nodes[i].constructor === classObject) + r.push(this._nodes[i]); + return r; +} + +/** +* Returns a list of nodes that matches a type +* @method findNodesByType +* @param {String} type the name of the node type +* @return {Array} a list with all the nodes of this type +*/ + +LGraph.prototype.findNodesByType = function(type) +{ + var type = type.toLowerCase(); + var r = []; + for(var i = 0, l = this._nodes.length; i < l; ++i) + if(this._nodes[i].type.toLowerCase() == type ) + r.push(this._nodes[i]); + return r; +} + +/** +* Returns a list of nodes that matches a name +* @method findNodesByTitle +* @param {String} name the name of the node to search +* @return {Array} a list with all the nodes with this name +*/ + +LGraph.prototype.findNodesByTitle = function(title) +{ + var result = []; + for(var i = 0, l = this._nodes.length; i < l; ++i) + if(this._nodes[i].title == title) + result.push(this._nodes[i]); + return result; +} + +/** +* Returns the top-most node in this position of the canvas +* @method getNodeOnPos +* @param {number} x the x coordinate in canvas space +* @param {number} y the y coordinate in canvas space +* @param {Array} nodes_list a list with all the nodes to search from, by default is all the nodes in the graph +* @return {LGraphNode} the node at this position or null +*/ +LGraph.prototype.getNodeOnPos = function(x,y, nodes_list) +{ + nodes_list = nodes_list || this._nodes; + for (var i = nodes_list.length - 1; i >= 0; i--) + { + var n = nodes_list[i]; + if(n.isPointInside( x, y, 2 )) + return n; + } + return null; +} + +/** +* Returns the top-most group in that position +* @method getGroupOnPos +* @param {number} x the x coordinate in canvas space +* @param {number} y the y coordinate in canvas space +* @return {LGraphGroup} the group or null +*/ +LGraph.prototype.getGroupOnPos = function(x,y) +{ + for (var i = this._groups.length - 1; i >= 0; i--) + { + var g = this._groups[i]; + if(g.isPointInside( x, y, 2 )) + return g; + } + return null; +} + +// ********** GLOBALS ***************** + +/** +* 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 }; + this._version++; + + if(this.onGlobalInputAdded) + this.onGlobalInputAdded(name, type); + + if(this.onGlobalsChange) + this.onGlobalsChange(); +} + +/** +* 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]; + if (!input) + return; + input.value = data; +} + +/** +* 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]; + if (!input) + return null; + return input.value; +} + +/** +* 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) + return; + + if(!this.global_inputs[old_name]) + return false; + + if(this.global_inputs[name]) + { + console.error("there is already one input with that name"); + return false; + } + + this.global_inputs[name] = this.global_inputs[old_name]; + delete this.global_inputs[old_name]; + this._version++; + + if(this.onGlobalInputRenamed) + this.onGlobalInputRenamed(old_name, name); + + if(this.onGlobalsChange) + 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]) + return false; + + if(this.global_inputs[name].type && this.global_inputs[name].type.toLowerCase() == type.toLowerCase() ) + return; + + this.global_inputs[name].type = type; + this._version++; + if(this.onGlobalInputTypeChanged) + 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]) + return false; + + delete this.global_inputs[name]; + this._version++; + + if(this.onGlobalInputRemoved) + this.onGlobalInputRemoved(name); + + if(this.onGlobalsChange) + this.onGlobalsChange(); + 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 }; + this._version++; + + if(this.onGlobalOutputAdded) + this.onGlobalOutputAdded(name, type); + + if(this.onGlobalsChange) + this.onGlobalsChange(); +} + +/** +* 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 ]; + if (!output) + return; + output.value = value; +} + +/** +* 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]; + if (!output) + return null; + 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; + + +/** +* 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]) + return false; + + if(this.global_outputs[name]) + { + console.error("there is already one output with that name"); + return false; + } + + this.global_outputs[name] = this.global_outputs[old_name]; + delete this.global_outputs[old_name]; + this._version++; + + if(this.onGlobalOutputRenamed) + this.onGlobalOutputRenamed(old_name, name); + + if(this.onGlobalsChange) + 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]) + return false; + + if(this.global_outputs[name].type && this.global_outputs[name].type.toLowerCase() == type.toLowerCase() ) + return; + + this.global_outputs[name].type = type; + this._version++; + if(this.onGlobalOutputTypeChanged) + this.onGlobalOutputTypeChanged(name, type); +} + +/** +* Removes a global graph output +* @method removeGlobalOutput +* @param {String} name +*/ +LGraph.prototype.removeGlobalOutput = function(name) +{ + if(!this.global_outputs[name]) + return false; + delete this.global_outputs[name]; + this._version++; + + if(this.onGlobalOutputRemoved) + this.onGlobalOutputRemoved(name); + + if(this.onGlobalsChange) + this.onGlobalsChange(); + return true; +} + +LGraph.prototype.triggerInput = function(name,value) +{ + 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.findNodesByTitle(name); + for(var i = 0; i < nodes.length; ++i) + nodes[i].setTrigger(func); +} + + +LGraph.prototype.connectionChange = function( node ) +{ + this.updateExecutionOrder(); + if( this.onConnectionChange ) + this.onConnectionChange( node ); + this._version++; + this.sendActionToCanvas("onConnectionChange"); +} + +/** +* returns if the graph is in live mode +* @method isLive +*/ + +LGraph.prototype.isLive = function() +{ + if(!this.list_of_graphcanvas) + return false; + + for(var i = 0; i < this.list_of_graphcanvas.length; ++i) + { + var c = this.list_of_graphcanvas[i]; + if(c.live_mode) + return true; + } + return false; +} + +/* Called when something visually changed (not the graph!) */ +LGraph.prototype.change = function() +{ + if(LiteGraph.debug) + console.log("Graph changed"); + this.sendActionToCanvas("setDirty",[true,true]); + if(this.on_change) + this.on_change(this); +} + +LGraph.prototype.setDirtyCanvas = function(fg,bg) +{ + this.sendActionToCanvas("setDirty",[fg,bg]); +} + +//save and recover app state *************************************** +/** +* Creates a Object containing all the info about this graph, it can be serialized +* @method serialize +* @return {Object} value of the node +*/ +LGraph.prototype.serialize = function() +{ + var nodes_info = []; + for(var i = 0, l = this._nodes.length; i < l; ++i) + nodes_info.push( this._nodes[i].serialize() ); + + //pack link info into a non-verbose format + var links = []; + for(var i in this.links) //links is an OBJECT + { + var link = this.links[i]; + links.push([ link.id, link.origin_id, link.origin_slot, link.target_id, link.target_slot, link.type ]); + } + + var groups_info = []; + for(var i = 0; i < this._groups.length; ++i) + groups_info.push( this._groups[i].serialize() ); + + var data = { + last_node_id: this.last_node_id, + last_link_id: this.last_link_id, + nodes: nodes_info, + links: links, + groups: groups_info, + config: this.config + }; + + return data; +} + + +/** +* Configure a graph from a JSON string +* @method configure +* @param {String} str configure a graph from a JSON string +* @param {Boolean} returns if there was any error parsing +*/ +LGraph.prototype.configure = function( data, keep_old ) +{ + if(!data) + return; + + if(!keep_old) + this.clear(); + + var nodes = data.nodes; + + //decode links info (they are very verbose) + if(data.links && data.links.constructor === Array) + { + var links = []; + for(var i = 0; i < data.links.length; ++i) + { + var link = data.links[i]; + links[ link[0] ] = { id: link[0], origin_id: link[1], origin_slot: link[2], target_id: link[3], target_slot: link[4], type: link[5] }; + } + data.links = links; + } + + //copy all stored fields + for (var i in data) + this[i] = data[i]; + + var error = false; + + //create nodes + this._nodes = []; + if(nodes) + { + for(var i = 0, l = nodes.length; i < l; ++i) + { + var n_info = nodes[i]; //stored info + var node = LiteGraph.createNode( n_info.type, n_info.title ); + if(!node) + { + if(LiteGraph.debug) + console.log("Node not found: " + n_info.type); + error = true; + continue; + } + + node.id = n_info.id; //id it or it will create a new id + this.add(node, true); //add before configure, otherwise configure cannot create links + } + + //configure nodes afterwards so they can reach each other + for(var i = 0, l = nodes.length; i < l; ++i) + { + var n_info = nodes[i]; + var node = this.getNodeById( n_info.id ); + if(node) + node.configure( n_info ); + } + } + + //groups + this._groups.length = 0; + if( data.groups ) + for(var i = 0; i < data.groups.length; ++i ) + { + var group = new LiteGraph.LGraphGroup(); + group.configure( data.groups[i] ); + this.add( group ); + } + + this.updateExecutionOrder(); + this._version++; + this.setDirtyCanvas(true,true); + return error; +} + +LGraph.prototype.load = function(url) +{ + var that = this; + var req = new XMLHttpRequest(); + req.open('GET', url, true); + req.send(null); + req.onload = function (oEvent) { + if(req.status !== 200) + { + console.error("Error loading graph:",req.status,req.response); + return; + } + var data = JSON.parse( req.response ); + that.configure(data); + } + req.onerror = function(err) + { + console.error("Error loading graph:",err); + } +} + +LGraph.prototype.onNodeTrace = function(node, msg, color) +{ + //TODO +} + +// ************************************************************* +// Node CLASS ******* +// ************************************************************* + +/* + title: string + pos: [x,y] + size: [x,y] + + input|output: every connection + + { name:string, type:string, pos: [x,y]=Optional, direction: "input"|"output", links: Array }); + + flags: + + clip_area: if you render outside the node, it will be cliped + + unsafe_execution: not allowed for safe execution + + skip_repeated_outputs: when adding new outputs, it wont show if there is one already connected + + resizable: if set to false it wont be resizable with the mouse + + supported callbacks: + + onAdded: when added to graph + + onRemoved: when removed from graph + + onStart: when the graph starts playing + + onStop: when the graph stops playing + + onDrawForeground: render the inside widgets inside the node + + onDrawBackground: render the background area inside the node (only in edit mode) + + onMouseDown + + onMouseMove + + onMouseUp + + onMouseEnter + + onMouseLeave + + onExecute: execute the node + + onPropertyChanged: when a property is changed in the panel (return true to skip default behaviour) + + onGetInputs: returns an array of possible inputs + + onGetOutputs: returns an array of possible outputs + + onDblClick + + onSerialize + + onSelected + + onDeselected + + onDropItem : DOM item dropped over the node + + onDropFile : file dropped over the node + + onConnectInput : if returns false the incoming connection will be canceled + + onConnectionsChange : a connection changed (new one or removed) (LiteGraph.INPUT or LiteGraph.OUTPUT, slot, true if connected, link_info, input_info ) +*/ + +/** +* Base Class for all the node type classes +* @class LGraphNode +* @param {String} name a name for the node +*/ + +function LGraphNode(title) +{ + this._ctor(title); +} + +global.LGraphNode = LiteGraph.LGraphNode = LGraphNode; + +LGraphNode.prototype._ctor = function( title ) +{ + this.title = title || "Unnamed"; + this.size = [LiteGraph.NODE_WIDTH,60]; + this.graph = null; + + this._pos = new Float32Array(10,10); + + Object.defineProperty( this, "pos", { + set: function(v) + { + if(!v || v.length < 2) + return; + this._pos[0] = v[0]; + this._pos[1] = v[1]; + }, + get: function() + { + return this._pos; + }, + enumerable: true + }); + + this.id = -1; //not know till not added + this.type = null; + + //inputs available: array of inputs + this.inputs = []; + this.outputs = []; + this.connections = []; + + //local data + this.properties = {}; //for the values + this.properties_info = []; //for the info + + this.data = null; //persistent local data + this.flags = {}; +} + +/** +* configure a node from an object containing the serialized info +* @method configure +*/ +LGraphNode.prototype.configure = function(info) +{ + if(this.graph) + this.graph._version++; + + for (var j in info) + { + if(j == "console") + continue; + + if(j == "properties") + { + //i dont want to clone properties, I want to reuse the old container + for(var k in info.properties) + { + this.properties[k] = info.properties[k]; + if(this.onPropertyChanged) + this.onPropertyChanged(k,info.properties[k]); + } + continue; + } + + if(info[j] == null) + continue; + + else if (typeof(info[j]) == 'object') //object + { + if(this[j] && this[j].configure) + this[j].configure( info[j] ); + else + this[j] = LiteGraph.cloneObject(info[j], this[j]); + } + else //value + this[j] = info[j]; + } + + if(!info.title) + this.title = this.constructor.title; + + if(this.onConnectionsChange) + { + if(this.inputs) + for(var i = 0; i < this.inputs.length; ++i) + { + var input = this.inputs[i]; + var link_info = this.graph ? this.graph.links[ input.link ] : null; + this.onConnectionsChange( LiteGraph.INPUT, i, true, link_info, input ); //link_info has been created now, so its updated + } + + if(this.outputs) + for(var i = 0; i < this.outputs.length; ++i) + { + var output = this.outputs[i]; + if(!output.links) + continue; + for(var j = 0; j < output.links.length; ++j) + { + var link_info = this.graph ? this.graph.links[ output.links[j] ] : null; + this.onConnectionsChange( LiteGraph.OUTPUT, i, true, link_info, output ); //link_info has been created now, so its updated + } + } + } + + //FOR LEGACY, PLEASE REMOVE ON NEXT VERSION + for(var i in this.inputs) + { + var input = this.inputs[i]; + if(!input.link || !input.link.length ) + continue; + var link = input.link; + if(typeof(link) != "object") + continue; + input.link = link[0]; + if(this.graph) + this.graph.links[ link[0] ] = { + id: link[0], + origin_id: link[1], + origin_slot: link[2], + target_id: link[3], + target_slot: link[4] + }; + } + for(var i in this.outputs) + { + var output = this.outputs[i]; + if(!output.links || output.links.length == 0) + continue; + for(var j in output.links) + { + var link = output.links[j]; + if(typeof(link) != "object") + continue; + output.links[j] = link[0]; + } + } + + if( this.onConfigure ) + this.onConfigure( info ); +} + +/** +* serialize the content +* @method serialize +*/ + +LGraphNode.prototype.serialize = function() +{ + //create serialization object + var o = { + id: this.id, + type: this.type, + pos: this.pos, + size: this.size, + data: this.data, + flags: LiteGraph.cloneObject(this.flags), + 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 ); + + if( !o.type ) + o.type = this.constructor.type; + + if( this.color ) + o.color = this.color; + if( this.bgcolor ) + o.bgcolor = this.bgcolor; + if( this.boxcolor ) + o.boxcolor = this.boxcolor; + if( this.shape ) + o.shape = this.shape; + + if(this.onSerialize) + this.onSerialize(o); + + return o; +} + + +/* Creates a clone of this node */ +LGraphNode.prototype.clone = function() +{ + var node = LiteGraph.createNode(this.type); + + //we clone it because serialize returns shared containers + var data = LiteGraph.cloneObject( this.serialize() ); + + //remove links + if(data.inputs) + for(var i = 0; i < data.inputs.length; ++i) + data.inputs[i].link = null; + + if(data.outputs) + for(var i = 0; i < data.outputs.length; ++i) + { + if(data.outputs[i].links) + data.outputs[i].links.length = 0; + } + + delete data["id"]; + //remove links + node.configure(data); + + return node; +} + + +/** +* serialize and stringify +* @method toString +*/ + +LGraphNode.prototype.toString = function() +{ + return JSON.stringify( this.serialize() ); +} +//LGraphNode.prototype.unserialize = function(info) {} //this cannot be done from within, must be done in LiteGraph + + +/** +* get the title string +* @method getTitle +*/ + +LGraphNode.prototype.getTitle = function() +{ + return this.title || this.constructor.title; +} + + + +// Execution ************************* +/** +* sets the output data +* @method setOutputData +* @param {number} slot +* @param {*} data +*/ +LGraphNode.prototype.setOutputData = function(slot, data) +{ + if(!this.outputs) + return; + + //this maybe slow and a niche case + //if(slot && slot.constructor === String) + // slot = this.findOutputSlot(slot); + + if(slot == -1 || slot >= this.outputs.length) + return; + + var output_info = this.outputs[slot]; + if(!output_info) + return; + + //store data in the output itself in case we want to debug + output_info._data = data; + + //if there are connections, pass the data to the connections + if( this.outputs[slot].links ) + { + for(var i = 0; i < this.outputs[slot].links.length; i++) + { + var link_id = this.outputs[slot].links[i]; + this.graph.links[ link_id ].data = data; + } + } +} + +/** +* 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 +* @return {*} data or if it is not connected returns undefined +*/ +LGraphNode.prototype.getInputData = function( slot, force_update ) +{ + if(!this.inputs) + return; //undefined; + + if(slot >= this.inputs.length || this.inputs[slot].link == null) + return; + + var link_id = this.inputs[slot].link; + var link = this.graph.links[ link_id ]; + if(!link) //bug: weird case but it happens sometimes + return null; + + 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; + + if(node.updateOutputData) + node.updateOutputData( link.origin_slot ); + else if(node.onExecute) + node.onExecute(); + + 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 +* @param {number} slot +* @return {boolean} +*/ +LGraphNode.prototype.isInputConnected = function(slot) +{ + if(!this.inputs) + return false; + return (slot < this.inputs.length && this.inputs[slot].link != null); +} + +/** +* tells you info about an input connection (which node, type, etc) +* @method getInputInfo +* @param {number} slot +* @return {Object} object or null { link: id, name: string, type: string or 0 } +*/ +LGraphNode.prototype.getInputInfo = function(slot) +{ + if(!this.inputs) + return null; + if(slot < this.inputs.length) + return this.inputs[slot]; + return null; +} + +/** +* returns the node connected in the input slot +* @method getInputNode +* @param {number} slot +* @return {LGraphNode} node or null +*/ +LGraphNode.prototype.getInputNode = function( slot ) +{ + if(!this.inputs) + return null; + if(slot >= this.inputs.length) + return null; + var input = this.inputs[slot]; + if(!input || input.link === null) + return null; + var link_info = this.graph.links[ input.link ]; + if(!link_info) + return null; + 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 +* @param {number} slot +* @return {Object} object or null +*/ +LGraphNode.prototype.getOutputData = function(slot) +{ + if(!this.outputs) + return null; + if(slot >= this.outputs.length) + return null; + + var info = this.outputs[slot]; + return info._data; +} + + +/** +* tells you info about an output connection (which node, type, etc) +* @method getOutputInfo +* @param {number} slot +* @return {Object} object or null { name: string, type: string, links: [ ids of links in number ] } +*/ +LGraphNode.prototype.getOutputInfo = function(slot) +{ + if(!this.outputs) + return null; + if(slot < this.outputs.length) + return this.outputs[slot]; + return null; +} + + +/** +* tells you if there is a connection in one output slot +* @method isOutputConnected +* @param {number} slot +* @return {boolean} +*/ +LGraphNode.prototype.isOutputConnected = function(slot) +{ + if(!this.outputs) + 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 +* @param {number} slot +* @return {array} +*/ +LGraphNode.prototype.getOutputNodes = function(slot) +{ + if(!this.outputs || this.outputs.length == 0) + return null; + + if(slot >= this.outputs.length) + return null; + + var output = this.outputs[slot]; + if(!output.links || output.links.length == 0) + return null; + + var r = []; + for(var i = 0; i < output.links.length; i++) + { + var link_id = output.links[i]; + var link = this.graph.links[ link_id ]; + if(link) + { + var target_node = this.graph.getNodeById( link.target_id ); + if( target_node ) + r.push( target_node ); + } + } + return r; +} + +/** +* Triggers an event in this node, this will trigger any output with the same name +* @method trigger +* @param {String} event name ( "on_play", ... ) if action is equivalent to false then the event is send to all +* @param {*} param +*/ +LGraphNode.prototype.trigger = function( action, param ) +{ + if( !this.outputs || !this.outputs.length ) + return; + + if(this.graph) + this.graph._last_trigger_time = LiteGraph.getTime(); + + for(var i = 0; i < this.outputs.length; ++i) + { + var output = this.outputs[ i ]; + if(!output || output.type !== LiteGraph.EVENT || (action && output.name != action) ) + continue; + this.triggerSlot( i, param ); + } +} + +/** +* Triggers an slot event in this node +* @method triggerSlot +* @param {Number} slot the index of the output slot +* @param {*} param +*/ +LGraphNode.prototype.triggerSlot = function( slot, param ) +{ + if( !this.outputs ) + return; + + var output = this.outputs[ slot ]; + if( !output ) + return; + + var links = output.links; + if(!links || !links.length) + return; + + if(this.graph) + this.graph._last_trigger_time = LiteGraph.getTime(); + + //for every link attached here + for(var k = 0; k < links.length; ++k) + { + var link_info = this.graph.links[ links[k] ]; + if(!link_info) //not connected + continue; + var node = this.graph.getNodeById( link_info.target_id ); + if(!node) //node not found? + continue; + + //used to mark events in graph + link_info._last_time = LiteGraph.getTime(); + + var target_connection = node.inputs[ link_info.target_slot ]; + + if(node.onAction) + node.onAction( target_connection.name, param ); + else if(node.mode === LiteGraph.ON_TRIGGER) + { + if(node.onExecute) + node.onExecute(param); + } + } +} + +/** +* add a new property to this node +* @method addProperty +* @param {string} name +* @param {*} default_value +* @param {string} type string defining the output type ("vec3","number",...) +* @param {Object} extra_info this can be used to have special properties of the property (like values, etc) +*/ +LGraphNode.prototype.addProperty = function( name, default_value, type, extra_info ) +{ + var o = { name: name, type: type, default_value: default_value }; + if(extra_info) + for(var i in extra_info) + o[i] = extra_info[i]; + if(!this.properties_info) + this.properties_info = []; + this.properties_info.push(o); + if(!this.properties) + this.properties = {}; + this.properties[ name ] = default_value; + return o; +} + + +//connections + +/** +* add a new output slot to use in this node +* @method addOutput +* @param {string} name +* @param {string} type string defining the output type ("vec3","number",...) +* @param {Object} extra_info this can be used to have special properties of an output (label, special color, position, etc) +*/ +LGraphNode.prototype.addOutput = function(name,type,extra_info) +{ + var o = { name: name, type: type, links: null }; + if(extra_info) + for(var i in extra_info) + o[i] = extra_info[i]; + + if(!this.outputs) + this.outputs = []; + this.outputs.push(o); + if(this.onOutputAdded) + this.onOutputAdded(o); + this.size = this.computeSize(); + return o; +} + +/** +* add a new output slot to use in this node +* @method addOutputs +* @param {Array} array of triplets like [[name,type,extra_info],[...]] +*/ +LGraphNode.prototype.addOutputs = function(array) +{ + for(var i = 0; i < array.length; ++i) + { + var info = array[i]; + var o = {name:info[0],type:info[1],link:null}; + if(array[2]) + for(var j in info[2]) + o[j] = info[2][j]; + + if(!this.outputs) + this.outputs = []; + this.outputs.push(o); + if(this.onOutputAdded) + this.onOutputAdded(o); + } + + this.size = this.computeSize(); +} + +/** +* remove an existing output slot +* @method removeOutput +* @param {number} slot +*/ +LGraphNode.prototype.removeOutput = function(slot) +{ + this.disconnectOutput(slot); + this.outputs.splice(slot,1); + this.size = this.computeSize(); + if(this.onOutputRemoved) + this.onOutputRemoved(slot); +} + +/** +* add a new input slot to use in this node +* @method addInput +* @param {string} name +* @param {string} type string defining the input type ("vec3","number",...), it its a generic one use 0 +* @param {Object} extra_info this can be used to have special properties of an input (label, color, position, etc) +*/ +LGraphNode.prototype.addInput = function(name,type,extra_info) +{ + type = type || 0; + var o = {name:name,type:type,link:null}; + if(extra_info) + for(var i in extra_info) + o[i] = extra_info[i]; + + if(!this.inputs) + this.inputs = []; + this.inputs.push(o); + this.size = this.computeSize(); + if(this.onInputAdded) + this.onInputAdded(o); + return o; +} + +/** +* add several new input slots in this node +* @method addInputs +* @param {Array} array of triplets like [[name,type,extra_info],[...]] +*/ +LGraphNode.prototype.addInputs = function(array) +{ + for(var i = 0; i < array.length; ++i) + { + var info = array[i]; + var o = {name:info[0], type:info[1], link:null}; + if(array[2]) + for(var j in info[2]) + o[j] = info[2][j]; + + if(!this.inputs) + this.inputs = []; + this.inputs.push(o); + if(this.onInputAdded) + this.onInputAdded(o); + } + + this.size = this.computeSize(); +} + +/** +* remove an existing input slot +* @method removeInput +* @param {number} slot +*/ +LGraphNode.prototype.removeInput = function(slot) +{ + this.disconnectInput(slot); + this.inputs.splice(slot,1); + this.size = this.computeSize(); + if(this.onInputRemoved) + this.onInputRemoved(slot); +} + +/** +* add an special connection to this node (used for special kinds of graphs) +* @method addConnection +* @param {string} name +* @param {string} type string defining the input type ("vec3","number",...) +* @param {[x,y]} pos position of the connection inside the node +* @param {string} direction if is input or output +*/ +LGraphNode.prototype.addConnection = function(name,type,pos,direction) +{ + var o = { + name: name, + type: type, + pos: pos, + direction: direction, + links: null + }; + this.connections.push( o ); + return o; +} + +/** +* computes the size of a node according to its inputs and output slots +* @method computeSize +* @param {number} minHeight +* @return {number} the total size +*/ +LGraphNode.prototype.computeSize = function( minHeight, out ) +{ + var rows = Math.max( this.inputs ? this.inputs.length : 1, this.outputs ? this.outputs.length : 1); + var size = out || new Float32Array([0,0]); + rows = Math.max(rows, 1); + var font_size = LiteGraph.NODE_TEXT_SIZE; //although it should be graphcanvas.inner_text_font size + size[1] = (this.constructor.slot_start_y || 0) + rows * (font_size + 1) + ( this.widgets ? this.widgets.length : 0 ) * (LiteGraph.NODE_WIDGET_HEIGHT + 4 ) + 4; + + var font_size = font_size; + var title_width = compute_text_size( this.title ); + var input_width = 0; + var output_width = 0; + + if(this.inputs) + for(var i = 0, l = this.inputs.length; i < l; ++i) + { + var input = this.inputs[i]; + var text = input.label || input.name || ""; + var text_width = compute_text_size( text ); + if(input_width < text_width) + input_width = text_width; + } + + if(this.outputs) + for(var i = 0, l = this.outputs.length; i < l; ++i) + { + var output = this.outputs[i]; + var text = output.label || output.name || ""; + var text_width = compute_text_size( text ); + if(output_width < text_width) + output_width = text_width; + } + + size[0] = Math.max( input_width + output_width + 10, title_width ); + size[0] = Math.max( size[0], LiteGraph.NODE_WIDTH ); + + if(this.onResize) + this.onResize(size); + + function compute_text_size( text ) + { + if(!text) + return 0; + return font_size * text.length * 0.6; + } + + return size; +} + +/** +* Allows to pass +* +* @method addWidget +* @return {Float32Array[4]} the total size +*/ +LGraphNode.prototype.addWidget = function( type, name, value, callback, options ) +{ + if(!this.widgets) + this.widgets = []; + var w = { + type: type.toLowerCase(), + name: name, + value: value, + callback: callback, + options: options || {} + }; + + if(w.options.y !== undefined ) + w.y = w.options.y; + + if( type == "combo" && !w.options.values ) + throw("LiteGraph addWidget('combo',...) requires to pass values in options: { values:['red','blue'] }"); + this.widgets.push(w); + return w; +} + + +/** +* returns the bounding of the object, used for rendering purposes +* bounding is: [topleft_cornerx, topleft_cornery, width, height] +* @method getBounding +* @return {Float32Array[4]} the total size +*/ +LGraphNode.prototype.getBounding = function( out ) +{ + out = out || new Float32Array(4); + out[0] = this.pos[0] - 4; + out[1] = this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT; + out[2] = this.size[0] + 4; + out[3] = this.size[1] + LiteGraph.NODE_TITLE_HEIGHT; + return out; +} + +/** +* checks if a point is inside the shape of a node +* @method isPointInside +* @param {number} x +* @param {number} y +* @return {boolean} +*/ +LGraphNode.prototype.isPointInside = function(x,y, margin) +{ + margin = margin || 0; + + var margin_top = this.graph && this.graph.isLive() ? 0 : 20; + if(this.flags && this.flags.collapsed) + { + //if ( distance([x,y], [this.pos[0] + this.size[0]*0.5, this.pos[1] + this.size[1]*0.5]) < LiteGraph.NODE_COLLAPSED_RADIUS) + if( isInsideRectangle( x, y, this.pos[0] - margin, this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT - margin, (this._collapsed_width||LiteGraph.NODE_COLLAPSED_WIDTH) + 2 * margin, LiteGraph.NODE_TITLE_HEIGHT + 2 * margin ) ) + return true; + } + else if ( (this.pos[0] - 4 - margin) < x && (this.pos[0] + this.size[0] + 4 + margin) > x + && (this.pos[1] - margin_top - margin) < y && (this.pos[1] + this.size[1] + margin) > y) + return true; + return false; +} + +/** +* checks if a point is inside a node slot, and returns info about which slot +* @method getSlotInPosition +* @param {number} x +* @param {number} y +* @return {Object} if found the object contains { input|output: slot object, slot: number, link_pos: [x,y] } +*/ +LGraphNode.prototype.getSlotInPosition = function( x, y ) +{ + //search for inputs + if(this.inputs) + for(var i = 0, l = this.inputs.length; i < l; ++i) + { + var input = this.inputs[i]; + var link_pos = this.getConnectionPos( true,i ); + if( isInsideRectangle(x, y, link_pos[0] - 10, link_pos[1] - 5, 20,10) ) + return { input: input, slot: i, link_pos: link_pos, locked: input.locked }; + } + + if(this.outputs) + for(var i = 0, l = this.outputs.length; i < l; ++i) + { + var output = this.outputs[i]; + var link_pos = this.getConnectionPos(false,i); + if( isInsideRectangle(x, y, link_pos[0] - 10, link_pos[1] - 5, 20,10) ) + return { output: output, slot: i, link_pos: link_pos, locked: output.locked }; + } + + return null; +} + +/** +* returns the input slot with a given name (used for dynamic slots), -1 if not found +* @method findInputSlot +* @param {string} name the name of the slot +* @return {number} the slot (-1 if not found) +*/ +LGraphNode.prototype.findInputSlot = function(name) +{ + if(!this.inputs) + return -1; + for(var i = 0, l = this.inputs.length; i < l; ++i) + if(name == this.inputs[i].name) + return i; + return -1; +} + +/** +* returns the output slot with a given name (used for dynamic slots), -1 if not found +* @method findOutputSlot +* @param {string} name the name of the slot +* @return {number} the slot (-1 if not found) +*/ +LGraphNode.prototype.findOutputSlot = function(name) +{ + if(!this.outputs) return -1; + for(var i = 0, l = this.outputs.length; i < l; ++i) + if(name == this.outputs[i].name) + return i; + return -1; +} + +/** +* connect this node output to the input of another node +* @method connect +* @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot) +* @param {LGraphNode} node the target node +* @param {number_or_string} target_slot the input slot of the target node (could be the number of the slot or the string with the name of the slot, or -1 to connect a trigger) +* @return {boolean} if it was connected succesfully +*/ +LGraphNode.prototype.connect = function( slot, target_node, target_slot ) +{ + target_slot = target_slot || 0; + + if(!this.graph) //could be connected before adding it to a graph + { + console.log("Connect: Error, node doesnt belong to any graph. Nodes must be added first to a graph before connecting them."); //due to link ids being associated with graphs + return false; + } + + + //seek for the output slot + if( slot.constructor === String ) + { + slot = this.findOutputSlot(slot); + if(slot == -1) + { + if(LiteGraph.debug) + console.log("Connect: Error, no slot of name " + slot); + return false; + } + } + else if(!this.outputs || slot >= this.outputs.length) + { + if(LiteGraph.debug) + console.log("Connect: Error, slot number not found"); + return false; + } + + if(target_node && target_node.constructor === Number) + target_node = this.graph.getNodeById( target_node ); + if(!target_node) + throw("target node is null"); + + //avoid loopback + if(target_node == this) + return false; + + //you can specify the slot by name + if(target_slot.constructor === String) + { + target_slot = target_node.findInputSlot( target_slot ); + if(target_slot == -1) + { + if(LiteGraph.debug) + console.log("Connect: Error, no slot of name " + target_slot); + return false; + } + } + else if( target_slot === LiteGraph.EVENT ) + { + //search for first slot with event? + /* + //create input for trigger + var input = target_node.addInput("onTrigger", LiteGraph.EVENT ); + target_slot = target_node.inputs.length - 1; //last one is the one created + target_node.mode = LiteGraph.ON_TRIGGER; + */ + return false; + } + else if( !target_node.inputs || target_slot >= target_node.inputs.length ) + { + if(LiteGraph.debug) + console.log("Connect: Error, slot number not found"); + return false; + } + + //if there is something already plugged there, disconnect + if(target_node.inputs[ target_slot ].link != null ) + target_node.disconnectInput( target_slot ); + + //why here?? + //this.setDirtyCanvas(false,true); + //this.graph.connectionChange( this ); + + var output = this.outputs[slot]; + + //allows nodes to block connection + if(target_node.onConnectInput) + if( target_node.onConnectInput( target_slot, output.type, output ) === false) + return false; + + var input = target_node.inputs[target_slot]; + + if( LiteGraph.isValidConnection( output.type, input.type ) ) + { + var link_info = { + id: this.graph.last_link_id++, + type: input.type, + origin_id: this.id, + origin_slot: slot, + target_id: target_node.id, + target_slot: target_slot + }; + + //add to graph links list + this.graph.links[ link_info.id ] = link_info; + + //connect in output + if( output.links == null ) + output.links = []; + output.links.push( link_info.id ); + //connect in input + target_node.inputs[target_slot].link = link_info.id; + if(this.graph) + this.graph._version++; + if(this.onConnectionsChange) + this.onConnectionsChange( LiteGraph.OUTPUT, slot, true, link_info, output ); //link_info has been created now, so its updated + if(target_node.onConnectionsChange) + target_node.onConnectionsChange( LiteGraph.INPUT, target_slot, true, link_info, input ); + if( this.graph && this.graph.onNodeConnectionChange ) + this.graph.onNodeConnectionChange( LiteGraph.OUTPUT, this, slot, target_node, target_slot ); + } + + this.setDirtyCanvas(false,true); + this.graph.connectionChange( this ); + + return true; +} + +/** +* disconnect one output to an specific node +* @method disconnectOutput +* @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot) +* @param {LGraphNode} target_node the target node to which this slot is connected [Optional, if not target_node is specified all nodes will be disconnected] +* @return {boolean} if it was disconnected succesfully +*/ +LGraphNode.prototype.disconnectOutput = function( slot, target_node ) +{ + if( slot.constructor === String ) + { + slot = this.findOutputSlot(slot); + if(slot == -1) + { + if(LiteGraph.debug) + console.log("Connect: Error, no slot of name " + slot); + return false; + } + } + else if(!this.outputs || slot >= this.outputs.length) + { + if(LiteGraph.debug) + console.log("Connect: Error, slot number not found"); + return false; + } + + //get output slot + var output = this.outputs[slot]; + if(!output.links || output.links.length == 0) + return false; + + //one of the output links in this slot + if(target_node) + { + if(target_node.constructor === Number) + target_node = this.graph.getNodeById( target_node ); + if(!target_node) + throw("Target Node not found"); + + for(var i = 0, l = output.links.length; i < l; i++) + { + var link_id = output.links[i]; + var link_info = this.graph.links[ link_id ]; + + //is the link we are searching for... + if( link_info.target_id == target_node.id ) + { + output.links.splice(i,1); //remove here + var input = target_node.inputs[ link_info.target_slot ]; + input.link = null; //remove there + delete this.graph.links[ link_id ]; //remove the link from the links pool + if(this.graph) + this.graph._version++; + if(target_node.onConnectionsChange) + target_node.onConnectionsChange( LiteGraph.INPUT, link_info.target_slot, false, link_info, input ); //link_info hasnt been modified so its ok + if(this.onConnectionsChange) + this.onConnectionsChange( LiteGraph.OUTPUT, slot, false, link_info, output ); + if( this.graph && this.graph.onNodeConnectionChange ) + this.graph.onNodeConnectionChange( LiteGraph.OUTPUT, this, slot ); + if( this.graph && this.graph.onNodeConnectionChange ) + this.graph.onNodeConnectionChange( LiteGraph.INPUT, target_node, link_info.target_slot ); + break; + } + } + } + else //all the links in this output slot + { + for(var i = 0, l = output.links.length; i < l; i++) + { + var link_id = output.links[i]; + var link_info = this.graph.links[ link_id ]; + if(!link_info) //bug: it happens sometimes + continue; + + var target_node = this.graph.getNodeById( link_info.target_id ); + var input = null; + if(this.graph) + this.graph._version++; + if(target_node) + { + input = target_node.inputs[ link_info.target_slot ]; + input.link = null; //remove other side link + if(target_node.onConnectionsChange) + target_node.onConnectionsChange( LiteGraph.INPUT, link_info.target_slot, false, link_info, input ); //link_info hasnt been modified so its ok + if( this.graph && this.graph.onNodeConnectionChange ) + this.graph.onNodeConnectionChange( LiteGraph.INPUT, target_node, link_info.target_slot ); + } + delete this.graph.links[ link_id ]; //remove the link from the links pool + if(this.onConnectionsChange) + this.onConnectionsChange( LiteGraph.OUTPUT, slot, false, link_info, output ); + if( this.graph && this.graph.onNodeConnectionChange ) + this.graph.onNodeConnectionChange( LiteGraph.OUTPUT, this, slot ); + } + output.links = null; + } + + + this.setDirtyCanvas(false,true); + this.graph.connectionChange( this ); + return true; +} + +/** +* disconnect one input +* @method disconnectInput +* @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot) +* @return {boolean} if it was disconnected succesfully +*/ +LGraphNode.prototype.disconnectInput = function( slot ) +{ + //seek for the output slot + if( slot.constructor === String ) + { + slot = this.findInputSlot(slot); + if(slot == -1) + { + if(LiteGraph.debug) + console.log("Connect: Error, no slot of name " + slot); + return false; + } + } + else if(!this.inputs || slot >= this.inputs.length) + { + if(LiteGraph.debug) + console.log("Connect: Error, slot number not found"); + return false; + } + + var input = this.inputs[slot]; + if(!input) + return false; + + var link_id = this.inputs[slot].link; + this.inputs[slot].link = null; + + //remove other side + var link_info = this.graph.links[ link_id ]; + if( link_info ) + { + var target_node = this.graph.getNodeById( link_info.origin_id ); + if(!target_node) + return false; + + var output = target_node.outputs[ link_info.origin_slot ]; + if(!output || !output.links || output.links.length == 0) + return false; + + //search in the inputs list for this link + for(var i = 0, l = output.links.length; i < l; i++) + { + if( output.links[i] == link_id ) + { + output.links.splice(i,1); + break; + } + } + + delete this.graph.links[ link_id ]; //remove from the pool + if(this.graph) + this.graph._version++; + if( this.onConnectionsChange ) + this.onConnectionsChange( LiteGraph.INPUT, slot, false, link_info, input ); + if( target_node.onConnectionsChange ) + target_node.onConnectionsChange( LiteGraph.OUTPUT, i, false, link_info, output ); + } + + this.setDirtyCanvas(false,true); + this.graph.connectionChange( this ); + return true; +} + +/** +* returns the center of a connection point in canvas coords +* @method getConnectionPos +* @param {boolean} is_input true if if a input slot, false if it is an output +* @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot) +* @return {[x,y]} the position +**/ +LGraphNode.prototype.getConnectionPos = function( is_input, slot_number ) +{ + if(this.flags.collapsed) + { + if(is_input) + return [this.pos[0], this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT * 0.5]; + else + return [this.pos[0] + (this._collapsed_width || LiteGraph.NODE_COLLAPSED_WIDTH), this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT * 0.5]; + } + + if(is_input && slot_number == -1) + { + return [this.pos[0] + 10, this.pos[1] + 10]; + } + + if(is_input && this.inputs.length > slot_number && this.inputs[slot_number].pos) + return [this.pos[0] + this.inputs[slot_number].pos[0],this.pos[1] + this.inputs[slot_number].pos[1]]; + else if(!is_input && this.outputs.length > slot_number && this.outputs[slot_number].pos) + return [this.pos[0] + this.outputs[slot_number].pos[0],this.pos[1] + this.outputs[slot_number].pos[1]]; + + //horizontal distributed slots + if(this.flags.horizontal) + { + if(is_input) + return [this.pos[0] + (slot_number + 0.5) * (this.size[0] / (this.inputs.length)), this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT ]; + return [this.pos[0] + (slot_number + 0.5) * (this.size[0] / (this.outputs.length)), this.pos[1] + this.size[1] ]; + } + + //default + if(is_input) + return [this.pos[0] , this.pos[1] + 10 + slot_number * LiteGraph.NODE_SLOT_HEIGHT + (this.constructor.slot_start_y || 0) ]; + return [this.pos[0] + this.size[0] + 1, this.pos[1] + 10 + slot_number * LiteGraph.NODE_SLOT_HEIGHT + (this.constructor.slot_start_y || 0)]; +} + +/* Force align to grid */ +LGraphNode.prototype.alignToGrid = function() +{ + this.pos[0] = LiteGraph.CANVAS_GRID_SIZE * Math.round(this.pos[0] / LiteGraph.CANVAS_GRID_SIZE); + this.pos[1] = LiteGraph.CANVAS_GRID_SIZE * Math.round(this.pos[1] / LiteGraph.CANVAS_GRID_SIZE); +} + + +/* Console output */ +LGraphNode.prototype.trace = function(msg) +{ + if(!this.console) + this.console = []; + this.console.push(msg); + if(this.console.length > LGraphNode.MAX_CONSOLE) + this.console.shift(); + + this.graph.onNodeTrace(this,msg); +} + +/* Forces to redraw or the main canvas (LGraphNode) or the bg canvas (links) */ +LGraphNode.prototype.setDirtyCanvas = function(dirty_foreground, dirty_background) +{ + if(!this.graph) + return; + this.graph.sendActionToCanvas("setDirty",[dirty_foreground, dirty_background]); +} + +LGraphNode.prototype.loadImage = function(url) +{ + var img = new Image(); + img.src = LiteGraph.node_images_path + url; + img.ready = false; + + var that = this; + img.onload = function() { + this.ready = true; + that.setDirtyCanvas(true); + } + return img; +} + +//safe LGraphNode action execution (not sure if safe) +/* +LGraphNode.prototype.executeAction = function(action) +{ + if(action == "") return false; + + if( action.indexOf(";") != -1 || action.indexOf("}") != -1) + { + this.trace("Error: Action contains unsafe characters"); + return false; + } + + var tokens = action.split("("); + var func_name = tokens[0]; + if( typeof(this[func_name]) != "function") + { + this.trace("Error: Action not found on node: " + func_name); + return false; + } + + var code = action; + + try + { + var _foo = eval; + eval = null; + (new Function("with(this) { " + code + "}")).call(this); + eval = _foo; + } + catch (err) + { + this.trace("Error executing action {" + action + "} :" + err); + return false; + } + + return true; +} +*/ + +/* Allows to get onMouseMove and onMouseUp events even if the mouse is out of focus */ +LGraphNode.prototype.captureInput = function(v) +{ + if(!this.graph || !this.graph.list_of_graphcanvas) + return; + + var list = this.graph.list_of_graphcanvas; + + for(var i = 0; i < list.length; ++i) + { + var c = list[i]; + //releasing somebody elses capture?! + if(!v && c.node_capturing_input != this) + continue; + + //change + c.node_capturing_input = v ? this : null; + } +} + +/** +* Collapse the node to make it smaller on the canvas +* @method collapse +**/ +LGraphNode.prototype.collapse = function( force ) +{ + this.graph._version++; + if(this.constructor.collapsable === false && !force) + return; + if(!this.flags.collapsed) + this.flags.collapsed = true; + else + this.flags.collapsed = false; + this.setDirtyCanvas(true,true); +} + +/** +* Forces the node to do not move or realign on Z +* @method pin +**/ + +LGraphNode.prototype.pin = function(v) +{ + this.graph._version++; + if(v === undefined) + this.flags.pinned = !this.flags.pinned; + else + this.flags.pinned = v; +} + +LGraphNode.prototype.localToScreen = function(x,y, graphcanvas) +{ + return [(x + this.pos[0]) * graphcanvas.scale + graphcanvas.offset[0], + (y + this.pos[1]) * graphcanvas.scale + graphcanvas.offset[1]]; +} + + + + +function LGraphGroup( title ) +{ + this._ctor( title ); +} + +global.LGraphGroup = LiteGraph.LGraphGroup = LGraphGroup; + +LGraphGroup.prototype._ctor = function( title ) +{ + this.title = title || "Group"; + this._bounding = new Float32Array([10,10,140,80]); + this._pos = this._bounding.subarray(0,2); + this._size = this._bounding.subarray(2,4); + this._nodes = []; + this.color = LGraphCanvas.node_colors.pale_blue ? LGraphCanvas.node_colors.pale_blue.groupcolor : "#AAA"; + this.graph = null; + + Object.defineProperty( this, "pos", { + set: function(v) + { + if(!v || v.length < 2) + return; + this._pos[0] = v[0]; + this._pos[1] = v[1]; + }, + get: function() + { + return this._pos; + }, + enumerable: true + }); + + Object.defineProperty( this, "size", { + set: function(v) + { + if(!v || v.length < 2) + return; + this._size[0] = Math.max(140,v[0]); + this._size[1] = Math.max(80,v[1]); + }, + get: function() + { + return this._size; + }, + enumerable: true + }); +} + +LGraphGroup.prototype.configure = function(o) +{ + this.title = o.title; + this._bounding.set( o.bounding ); + this.color = o.color; +} + +LGraphGroup.prototype.serialize = function() +{ + var b = this._bounding; + return { + title: this.title, + bounding: [ b[0], b[1], b[2], b[3] ], + color: this.color + }; +} + +LGraphGroup.prototype.move = function(deltax, deltay, ignore_nodes) +{ + this._pos[0] += deltax; + this._pos[1] += deltay; + if(ignore_nodes) + return; + for(var i = 0; i < this._nodes.length; ++i) + { + var node = this._nodes[i]; + node.pos[0] += deltax; + node.pos[1] += deltay; + } +} + +LGraphGroup.prototype.recomputeInsideNodes = function() +{ + this._nodes.length = 0; + var nodes = this.graph._nodes; + var node_bounding = new Float32Array(4); + + for(var i = 0; i < nodes.length; ++i) + { + var node = nodes[i]; + node.getBounding( node_bounding ); + if(!overlapBounding( this._bounding, node_bounding )) + continue; //out of the visible area + this._nodes.push( node ); + } +} + +LGraphGroup.prototype.isPointInside = LGraphNode.prototype.isPointInside; +LGraphGroup.prototype.setDirtyCanvas = LGraphNode.prototype.setDirtyCanvas; + +//********************************************************************************* +// LGraphCanvas: LGraph renderer CLASS +//********************************************************************************* + +/** +* This class is in charge of rendering one graph inside a canvas. And provides all the interaction required. +* Valid callbacks are: onNodeSelected, onNodeDeselected, onShowNodePanel, onNodeDblClicked +* +* @class LGraphCanvas +* @constructor +* @param {HTMLCanvas} canvas the canvas where you want to render (it accepts a selector in string format or the canvas element itself) +* @param {LGraph} graph [optional] +* @param {Object} options [optional] { skip_rendering, autoresize } +*/ +function LGraphCanvas( canvas, graph, options ) +{ + options = options || {}; + + //if(graph === undefined) + // throw ("No graph assigned"); + this.background_image = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQBJREFUeNrs1rEKwjAUhlETUkj3vP9rdmr1Ysammk2w5wdxuLgcMHyptfawuZX4pJSWZTnfnu/lnIe/jNNxHHGNn//HNbbv+4dr6V+11uF527arU7+u63qfa/bnmh8sWLBgwYJlqRf8MEptXPBXJXa37BSl3ixYsGDBMliwFLyCV/DeLIMFCxYsWLBMwSt4Be/NggXLYMGCBUvBK3iNruC9WbBgwYJlsGApeAWv4L1ZBgsWLFiwYJmCV/AK3psFC5bBggULloJX8BpdwXuzYMGCBctgwVLwCl7Be7MMFixYsGDBsu8FH1FaSmExVfAxBa/gvVmwYMGCZbBg/W4vAQYA5tRF9QYlv/QAAAAASUVORK5CYII=' + + if(canvas && canvas.constructor === String ) + canvas = document.querySelector( canvas ); + + this.max_zoom = 10; + this.min_zoom = 0.1; + this.zoom_modify_alpha = true; //otherwise it generates ugly patterns when scaling down too much + + this.title_text_font = "bold "+LiteGraph.NODE_TEXT_SIZE+"px Arial"; + this.inner_text_font = "normal "+LiteGraph.NODE_SUBTEXT_SIZE+"px Arial"; + this.node_title_color = LiteGraph.NODE_TITLE_COLOR; + this.default_link_color = LiteGraph.LINK_COLOR; + this.default_connection_color = { + input_off: "#AAB", + input_on: "#7F7", + output_off: "#AAB", + output_on: "#7F7" + }; + + this.highquality_render = true; + this.use_gradients = false; //set to true to render titlebar with gradients + this.editor_alpha = 1; //used for transition + this.pause_rendering = false; + this.render_shadows = true; + this.clear_background = true; + + this.render_only_selected = true; + this.live_mode = false; + this.show_info = true; + this.allow_dragcanvas = true; + this.allow_dragnodes = true; + this.allow_interaction = true; //allow to control widgets, buttons, collapse, etc + this.allow_searchbox = true; + this.drag_mode = false; + this.dragging_rectangle = null; + + this.filter = null; //allows to filter to only accept some type of nodes in a graph + + this.always_render_background = false; + this.render_canvas_border = true; + this.render_connections_shadows = false; //too much cpu + this.render_connections_border = true; + this.render_curved_connections = true; + this.render_connection_arrows = true; + + this.canvas_mouse = [0,0]; //mouse in canvas graph coordinates, where 0,0 is the top-left corner of the blue rectangle + + //to personalize the search box + this.onSearchBox = null; + this.onSearchBoxSelection = null; + + this.connections_width = 3; + this.round_radius = 8; + + this.current_node = null; + this.node_widget = null; //used for widgets + this.last_mouse_position = [0,0]; + + //link canvas and graph + if(graph) + graph.attachCanvas(this); + + this.setCanvas( canvas ); + this.clear(); + + if(!options.skip_render) + this.startRendering(); + + this.autoresize = options.autoresize; +} + +global.LGraphCanvas = LiteGraph.LGraphCanvas = LGraphCanvas; + +LGraphCanvas.link_type_colors = {"-1":"#F85",'number':"#AAC","node":"#DCA"}; +LGraphCanvas.gradients = {}; //cache of gradients + +/** +* clears all the data inside +* +* @method clear +*/ +LGraphCanvas.prototype.clear = function() +{ + this.frame = 0; + this.last_draw_time = 0; + this.render_time = 0; + this.fps = 0; + + this.scale = 1; + this.offset = [0,0]; + + this.dragging_rectangle = null; + + this.selected_nodes = {}; + this.selected_group = null; + + this.visible_nodes = []; + this.node_dragged = null; + this.node_over = null; + this.node_capturing_input = null; + this.connecting_node = null; + this.highlighted_links = {}; + + this.dirty_canvas = true; + this.dirty_bgcanvas = true; + this.dirty_area = null; + + this.node_in_panel = null; + this.node_widget = null; + + this.last_mouse = [0,0]; + this.last_mouseclick = 0; + + if(this.onClear) + this.onClear(); + //this.UIinit(); +} + +/** +* assigns a graph, you can reasign graphs to the same canvas +* +* @method setGraph +* @param {LGraph} graph +*/ +LGraphCanvas.prototype.setGraph = function( graph, skip_clear ) +{ + if(this.graph == graph) + return; + + if(!skip_clear) + this.clear(); + + if(!graph && this.graph) + { + this.graph.detachCanvas(this); + return; + } + + /* + if(this.graph) + this.graph.canvas = null; //remove old graph link to the canvas + this.graph = graph; + if(this.graph) + this.graph.canvas = this; + */ + graph.attachCanvas(this); + this.setDirty(true,true); +} + +/** +* opens a graph contained inside a node in the current graph +* +* @method openSubgraph +* @param {LGraph} graph +*/ +LGraphCanvas.prototype.openSubgraph = function(graph) +{ + if(!graph) + throw("graph cannot be null"); + + if(this.graph == graph) + throw("graph cannot be the same"); + + this.clear(); + + if(this.graph) + { + if(!this._graph_stack) + this._graph_stack = []; + this._graph_stack.push(this.graph); + } + + graph.attachCanvas(this); + this.setDirty(true,true); +} + +/** +* closes a subgraph contained inside a node +* +* @method closeSubgraph +* @param {LGraph} assigns a graph +*/ +LGraphCanvas.prototype.closeSubgraph = function() +{ + if(!this._graph_stack || this._graph_stack.length == 0) + return; + var graph = this._graph_stack.pop(); + this.selected_nodes = {}; + this.highlighted_links = {}; + graph.attachCanvas(this); + this.setDirty(true,true); +} + +/** +* assigns a canvas +* +* @method setCanvas +* @param {Canvas} assigns a canvas (also accepts the ID of the element (not a selector) +*/ +LGraphCanvas.prototype.setCanvas = function( canvas, skip_events ) +{ + var that = this; + + if(canvas) + { + if( canvas.constructor === String ) + { + canvas = document.getElementById(canvas); + if(!canvas) + throw("Error creating LiteGraph canvas: Canvas not found"); + } + } + + if(canvas === this.canvas) + return; + + if(!canvas && this.canvas) + { + //maybe detach events from old_canvas + if(!skip_events) + this.unbindEvents(); + } + + this.canvas = canvas; + + if(!canvas) + return; + + //this.canvas.tabindex = "1000"; + canvas.className += " lgraphcanvas"; + canvas.data = this; + + //bg canvas: used for non changing stuff + this.bgcanvas = null; + if(!this.bgcanvas) + { + this.bgcanvas = document.createElement("canvas"); + this.bgcanvas.width = this.canvas.width; + this.bgcanvas.height = this.canvas.height; + } + + if(canvas.getContext == null) + { + if( canvas.localName != "canvas" ) + throw("Element supplied for LGraphCanvas must be a element, you passed a " + canvas.localName ); + throw("This browser doesnt support Canvas"); + } + + var ctx = this.ctx = canvas.getContext("2d"); + if(ctx == null) + { + if(!canvas.webgl_enabled) + console.warn("This canvas seems to be WebGL, enabling WebGL renderer"); + this.enableWebGL(); + } + + //input: (move and up could be unbinded) + this._mousemove_callback = this.processMouseMove.bind(this); + this._mouseup_callback = this.processMouseUp.bind(this); + + if(!skip_events) + this.bindEvents(); +} + +//used in some events to capture them +LGraphCanvas.prototype._doNothing = function doNothing(e) { e.preventDefault(); return false; }; +LGraphCanvas.prototype._doReturnTrue = function doNothing(e) { e.preventDefault(); return true; }; + +/** +* binds mouse, keyboard, touch and drag events to the canvas +* @method bindEvents +**/ +LGraphCanvas.prototype.bindEvents = function() +{ + if( this._events_binded ) + { + console.warn("LGraphCanvas: events already binded"); + return; + } + + var canvas = this.canvas; + var ref_window = this.getCanvasWindow(); + var document = ref_window.document; //hack used when moving canvas between windows + + this._mousedown_callback = this.processMouseDown.bind(this); + this._mousewheel_callback = this.processMouseWheel.bind(this); + + canvas.addEventListener("mousedown", this._mousedown_callback, true ); //down do not need to store the binded + canvas.addEventListener("mousemove", this._mousemove_callback ); + canvas.addEventListener("mousewheel", this._mousewheel_callback, false); + + canvas.addEventListener("contextmenu", this._doNothing ); + canvas.addEventListener("DOMMouseScroll", this._mousewheel_callback, false); + + //touch events + //if( 'touchstart' in document.documentElement ) + { + canvas.addEventListener("touchstart", this.touchHandler, true); + canvas.addEventListener("touchmove", this.touchHandler, true); + canvas.addEventListener("touchend", this.touchHandler, true); + canvas.addEventListener("touchcancel", this.touchHandler, true); + } + + //Keyboard ****************** + this._key_callback = this.processKey.bind(this); + + canvas.addEventListener("keydown", this._key_callback, true ); + document.addEventListener("keyup", this._key_callback, true ); //in document, otherwise it doesnt fire keyup + + //Droping Stuff over nodes ************************************ + this._ondrop_callback = this.processDrop.bind(this); + + canvas.addEventListener("dragover", this._doNothing, false ); + canvas.addEventListener("dragend", this._doNothing, false ); + canvas.addEventListener("drop", this._ondrop_callback, false ); + canvas.addEventListener("dragenter", this._doReturnTrue, false ); + + this._events_binded = true; +} + +/** +* unbinds mouse events from the canvas +* @method unbindEvents +**/ +LGraphCanvas.prototype.unbindEvents = function() +{ + if( !this._events_binded ) + { + console.warn("LGraphCanvas: no events binded"); + return; + } + + var ref_window = this.getCanvasWindow(); + var document = ref_window.document; + + this.canvas.removeEventListener( "mousedown", this._mousedown_callback ); + this.canvas.removeEventListener( "mousewheel", this._mousewheel_callback ); + this.canvas.removeEventListener( "DOMMouseScroll", this._mousewheel_callback ); + this.canvas.removeEventListener( "keydown", this._key_callback ); + document.removeEventListener( "keyup", this._key_callback ); + this.canvas.removeEventListener( "contextmenu", this._doNothing ); + this.canvas.removeEventListener( "drop", this._ondrop_callback ); + this.canvas.removeEventListener( "dragenter", this._doReturnTrue ); + + this.canvas.removeEventListener("touchstart", this.touchHandler ); + this.canvas.removeEventListener("touchmove", this.touchHandler ); + this.canvas.removeEventListener("touchend", this.touchHandler ); + this.canvas.removeEventListener("touchcancel", this.touchHandler ); + + this._mousedown_callback = null; + this._mousewheel_callback = null; + this._key_callback = null; + this._ondrop_callback = null; + + this._events_binded = false; +} + +LGraphCanvas.getFileExtension = function (url) +{ + var question = url.indexOf("?"); + if(question != -1) + url = url.substr(0,question); + var point = url.lastIndexOf("."); + if(point == -1) + return ""; + return url.substr(point+1).toLowerCase(); +} + +/** +* this function allows to render the canvas using WebGL instead of Canvas2D +* this is useful if you plant to render 3D objects inside your nodes, it uses litegl.js for webgl and canvas2DtoWebGL to emulate the Canvas2D calls in webGL +* @method enableWebGL +**/ +LGraphCanvas.prototype.enableWebGL = function() +{ + if(typeof(GL) === undefined) + throw("litegl.js must be included to use a WebGL canvas"); + if(typeof(enableWebGLCanvas) === undefined) + throw("webglCanvas.js must be included to use this feature"); + + this.gl = this.ctx = enableWebGLCanvas(this.canvas); + this.ctx.webgl = true; + this.bgcanvas = this.canvas; + this.bgctx = this.gl; + this.canvas.webgl_enabled = true; + + /* + GL.create({ canvas: this.bgcanvas }); + this.bgctx = enableWebGLCanvas( this.bgcanvas ); + window.gl = this.gl; + */ +} + + +/** +* marks as dirty the canvas, this way it will be rendered again +* +* @class LGraphCanvas +* @method setDirty +* @param {bool} fgcanvas if the foreground canvas is dirty (the one containing the nodes) +* @param {bool} bgcanvas if the background canvas is dirty (the one containing the wires) +*/ +LGraphCanvas.prototype.setDirty = function( fgcanvas, bgcanvas ) +{ + if(fgcanvas) + this.dirty_canvas = true; + if(bgcanvas) + this.dirty_bgcanvas = true; +} + +/** +* Used to attach the canvas in a popup +* +* @method getCanvasWindow +* @return {window} returns the window where the canvas is attached (the DOM root node) +*/ +LGraphCanvas.prototype.getCanvasWindow = function() +{ + if(!this.canvas) + return window; + var doc = this.canvas.ownerDocument; + return doc.defaultView || doc.parentWindow; +} + +/** +* starts rendering the content of the canvas when needed +* +* @method startRendering +*/ +LGraphCanvas.prototype.startRendering = function() +{ + if(this.is_rendering) + return; //already rendering + + this.is_rendering = true; + renderFrame.call(this); + + function renderFrame() + { + if(!this.pause_rendering) + this.draw(); + + var window = this.getCanvasWindow(); + if(this.is_rendering) + window.requestAnimationFrame( renderFrame.bind(this) ); + } +} + +/** +* stops rendering the content of the canvas (to save resources) +* +* @method stopRendering +*/ +LGraphCanvas.prototype.stopRendering = function() +{ + this.is_rendering = false; + /* + if(this.rendering_timer_id) + { + clearInterval(this.rendering_timer_id); + this.rendering_timer_id = null; + } + */ +} + +/* LiteGraphCanvas input */ + +LGraphCanvas.prototype.processMouseDown = function(e) +{ + if(!this.graph) + return; + + this.adjustMouseEvent(e); + + var ref_window = this.getCanvasWindow(); + var document = ref_window.document; + LGraphCanvas.active_canvas = this; + var that = this; + + //move mouse move event to the window in case it drags outside of the canvas + this.canvas.removeEventListener("mousemove", this._mousemove_callback ); + ref_window.document.addEventListener("mousemove", this._mousemove_callback, true ); //catch for the entire window + ref_window.document.addEventListener("mouseup", this._mouseup_callback, true ); + + var node = this.graph.getNodeOnPos( e.canvasX, e.canvasY, this.visible_nodes ); + var skip_dragging = false; + var skip_action = false; + var now = LiteGraph.getTime(); + var is_double_click = (now - this.last_mouseclick) < 300; + + this.canvas_mouse[0] = e.canvasX; + this.canvas_mouse[1] = e.canvasY; + + LiteGraph.closeAllContextMenus( ref_window ); + + if(e.which == 1) //left button mouse + { + if( e.ctrlKey ) + { + this.dragging_rectangle = new Float32Array(4); + this.dragging_rectangle[0] = e.canvasX; + this.dragging_rectangle[1] = e.canvasY; + this.dragging_rectangle[2] = 1; + this.dragging_rectangle[3] = 1; + skip_action = true; + } + + var clicking_canvas_bg = false; + + //when clicked on top of a node + //and it is not interactive + if( node && this.allow_interaction && !skip_action ) + { + if( !this.live_mode && !node.flags.pinned ) + this.bringToFront( node ); //if it wasnt selected? + + //not dragging mouse to connect two slots + if(!this.connecting_node && !node.flags.collapsed && !this.live_mode) + { + //search for outputs + if(node.outputs) + for(var i = 0, l = node.outputs.length; i < l; ++i) + { + var output = node.outputs[i]; + var link_pos = node.getConnectionPos(false,i); + if( isInsideRectangle( e.canvasX, e.canvasY, link_pos[0] - 10, link_pos[1] - 5, 20,10) ) + { + this.connecting_node = node; + this.connecting_output = output; + this.connecting_pos = node.getConnectionPos(false,i); + this.connecting_slot = i; + + if (is_double_click) { + if (node.onOutputDblClick) + node.onOutputDblClick(i, e); + } else { + if (node.onOutputClick) + node.onOutputClick(i, e); + } + + skip_action = true; + break; + } + } + + //search for inputs + if(node.inputs) + for(var i = 0, l = node.inputs.length; i < l; ++i) + { + var input = node.inputs[i]; + var link_pos = node.getConnectionPos( true, i ); + if( isInsideRectangle(e.canvasX, e.canvasY, link_pos[0] - 10, link_pos[1] - 5, 20,10) ) + { + if (is_double_click) { + if (node.onInputDblClick) + node.onInputDblClick(i, e); + } else { + if (node.onInputClick) + node.onInputClick(i, e); + } + + if(input.link !== null) + { + node.disconnectInput(i); + this.dirty_bgcanvas = true; + skip_action = true; + } + } + } + + //Search for corner + if( !skip_action && node.flags.resizable !== false && isInsideRectangle(e.canvasX, e.canvasY, node.pos[0] + node.size[0] - 5, node.pos[1] + node.size[1] - 5 ,5,5 )) + { + this.resizing_node = node; + this.canvas.style.cursor = "se-resize"; + skip_action = true; + } + } + + //Search for corner + if( !skip_action && isInsideRectangle(e.canvasX, e.canvasY, node.pos[0], node.pos[1] - LiteGraph.NODE_TITLE_HEIGHT, LiteGraph.NODE_TITLE_HEIGHT, LiteGraph.NODE_TITLE_HEIGHT )) + { + node.collapse(); + skip_action = true; + } + + //it wasnt clicked on the links boxes + if(!skip_action) + { + var block_drag_node = false; + + //widgets + var widget = this.processNodeWidgets( node, this.canvas_mouse, e ); + if(widget) + { + block_drag_node = true; + this.node_widget = [node, widget]; + } + + //double clicking + if (is_double_click && this.selected_nodes[ node.id ]) + { + //double click node + if( node.onDblClick) + node.onDblClick(e); + this.processNodeDblClicked( node ); + block_drag_node = true; + } + + //if do not capture mouse + if( node.onMouseDown && node.onMouseDown( e, [e.canvasX - node.pos[0], e.canvasY - node.pos[1]] ) ) + block_drag_node = true; + else if(this.live_mode) + { + clicking_canvas_bg = true; + block_drag_node = true; + } + + if(!block_drag_node) + { + if(this.allow_dragnodes) + this.node_dragged = node; + if(!this.selected_nodes[ node.id ]) + this.processNodeSelected( node, e ); + } + + this.dirty_canvas = true; + } + } + else //clicked outside of nodes + { + this.selected_group = this.graph.getGroupOnPos( e.canvasX, e.canvasY ); + this.selected_group_resizing = false; + if( this.selected_group ) + { + if( e.ctrlKey ) + this.dragging_rectangle = null; + + var dist = distance( [e.canvasX, e.canvasY], [ this.selected_group.pos[0] + this.selected_group.size[0], this.selected_group.pos[1] + this.selected_group.size[1] ] ); + if( (dist * this.scale) < 10 ) + this.selected_group_resizing = true; + else + this.selected_group.recomputeInsideNodes(); + } + + if( is_double_click ) + this.showSearchBox( e ); + + clicking_canvas_bg = true; + } + + if( !skip_action && clicking_canvas_bg && this.allow_dragcanvas ) + { + this.dragging_canvas = true; + } + } + else if (e.which == 2) //middle button + { + + } + else if (e.which == 3) //right button + { + this.processContextMenu( node, e ); + } + + //TODO + //if(this.node_selected != prev_selected) + // this.onNodeSelectionChange(this.node_selected); + + this.last_mouse[0] = e.localX; + this.last_mouse[1] = e.localY; + this.last_mouseclick = LiteGraph.getTime(); + + /* + if( (this.dirty_canvas || this.dirty_bgcanvas) && this.rendering_timer_id == null) + this.draw(); + */ + + this.graph.change(); + + //this is to ensure to defocus(blur) if a text input element is on focus + if(!ref_window.document.activeElement || (ref_window.document.activeElement.nodeName.toLowerCase() != "input" && ref_window.document.activeElement.nodeName.toLowerCase() != "textarea")) + e.preventDefault(); + e.stopPropagation(); + + if(this.onMouseDown) + this.onMouseDown(e); + + return false; +} + +/** +* Called when a mouse move event has to be processed +* @method processMouseMove +**/ +LGraphCanvas.prototype.processMouseMove = function(e) +{ + if(this.autoresize) + this.resize(); + + if(!this.graph) + return; + + LGraphCanvas.active_canvas = this; + this.adjustMouseEvent(e); + var mouse = [e.localX, e.localY]; + 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; + + if( this.node_widget ) + { + this.processNodeWidgets( this.node_widget[0], this.canvas_mouse, e, this.node_widget[1] ); + this.dirty_canvas = true; + } + + 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) + { + if( this.selected_group_resizing ) + this.selected_group.size = [ e.canvasX - this.selected_group.pos[0], e.canvasY - this.selected_group.pos[1] ]; + else + { + var deltax = delta[0] / this.scale; + var deltay = delta[1] / this.scale; + this.selected_group.move( deltax, deltay, e.ctrlKey ); + if( this.selected_group._nodes.length) + this.dirty_canvas = true; + } + this.dirty_bgcanvas = true; + } + else if(this.dragging_canvas) + { + this.offset[0] += delta[0] / this.scale; + this.offset[1] += delta[1] / this.scale; + this.dirty_canvas = true; + this.dirty_bgcanvas = true; + } + else if(this.allow_interaction) + { + if(this.connecting_node) + this.dirty_canvas = true; + + //get node over + 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]) + { + //mouse leave + this.graph._nodes[i].mouseOver = false; + if(this.node_over && this.node_over.onMouseLeave) + this.node_over.onMouseLeave(e); + this.node_over = null; + this.dirty_canvas = true; + } + } + + //mouse over a node + if(node) + { + //this.canvas.style.cursor = "move"; + if(!node.mouseOver) + { + //mouse enter + node.mouseOver = true; + this.node_over = node; + this.dirty_canvas = true; + + if(node.onMouseEnter) node.onMouseEnter(e); + } + + //in case the node wants to do something + if(node.onMouseMove) + node.onMouseMove(e); + + //if dragging a link + if(this.connecting_node) + { + var pos = this._highlight_input || [0,0]; //to store the output of isOverNodeInput + + //on top of input + if( this.isOverNodeBox( node, e.canvasX, e.canvasY ) ) + { + //mouse on top of the corner box, dont know what to do + } + else + { + //check if I have a slot below de mouse + 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 ) ) + this._highlight_input = pos; + } + else + this._highlight_input = null; + } + } + + //Search for corner + if(this.canvas) + { + if( isInsideRectangle(e.canvasX, e.canvasY, node.pos[0] + node.size[0] - 5, node.pos[1] + node.size[1] - 5 ,5,5 )) + this.canvas.style.cursor = "se-resize"; + else + this.canvas.style.cursor = null; + } + } + else if(this.canvas) + this.canvas.style.cursor = null; + + if(this.node_capturing_input && this.node_capturing_input != node && this.node_capturing_input.onMouseMove) + { + this.node_capturing_input.onMouseMove(e); + } + + + if(this.node_dragged && !this.live_mode) + { + /* + this.node_dragged.pos[0] += delta[0] / this.scale; + this.node_dragged.pos[1] += delta[1] / this.scale; + this.node_dragged.pos[0] = Math.round(this.node_dragged.pos[0]); + this.node_dragged.pos[1] = Math.round(this.node_dragged.pos[1]); + */ + + for(var i in this.selected_nodes) + { + var n = this.selected_nodes[i]; + + n.pos[0] += delta[0] / this.scale; + n.pos[1] += delta[1] / this.scale; + //n.pos[0] = Math.round(n.pos[0]); + //n.pos[1] = Math.round(n.pos[1]); + } + + this.dirty_canvas = true; + this.dirty_bgcanvas = true; + } + + if(this.resizing_node && !this.live_mode) + { + //convert mouse to node space + this.resizing_node.size[0] = e.canvasX - this.resizing_node.pos[0]; + this.resizing_node.size[1] = e.canvasY - this.resizing_node.pos[1]; + + //constraint size + var max_slots = Math.max( this.resizing_node.inputs ? this.resizing_node.inputs.length : 0, this.resizing_node.outputs ? this.resizing_node.outputs.length : 0); + var min_height = max_slots * LiteGraph.NODE_SLOT_HEIGHT + ( this.resizing_node.widgets ? this.resizing_node.widgets.length : 0 ) * (LiteGraph.NODE_WIDGET_HEIGHT + 4 ) + 4; + if(this.resizing_node.size[1] < min_height ) + this.resizing_node.size[1] = min_height; + if(this.resizing_node.size[0] < LiteGraph.NODE_MIN_WIDTH) + this.resizing_node.size[0] = LiteGraph.NODE_MIN_WIDTH; + + this.canvas.style.cursor = "se-resize"; + this.dirty_canvas = true; + this.dirty_bgcanvas = true; + } + } + + /* + if((this.dirty_canvas || this.dirty_bgcanvas) && this.rendering_timer_id == null) + this.draw(); + */ + + e.preventDefault(); + //e.stopPropagation(); + return false; + //this is not really optimal + //this.graph.change(); +} + +/** +* Called when a mouse up event has to be processed +* @method processMouseUp +**/ +LGraphCanvas.prototype.processMouseUp = function(e) +{ + if(!this.graph) + return; + + var window = this.getCanvasWindow(); + var document = window.document; + LGraphCanvas.active_canvas = this; + + //restore the mousemove event back to the canvas + document.removeEventListener("mousemove", this._mousemove_callback, true ); + this.canvas.addEventListener("mousemove", this._mousemove_callback, true); + document.removeEventListener("mouseup", this._mouseup_callback, true ); + + this.adjustMouseEvent(e); + var now = LiteGraph.getTime(); + e.click_time = (now - this.last_mouseclick); + + if (e.which == 1) //left button + { + this.node_widget = null; + this.selected_group = null; + this.selected_group_resizing = false; + + if( this.dragging_rectangle ) + { + if(this.graph) + { + var nodes = this.graph._nodes; + var node_bounding = new Float32Array(4); + this.deselectAllNodes(); + if( this.dragging_rectangle[2] < 0 ) //flip if negative width + this.dragging_rectangle[0] += this.dragging_rectangle[2]; + if( this.dragging_rectangle[3] < 0 ) //flip if negative height + this.dragging_rectangle[1] += this.dragging_rectangle[3]; + this.dragging_rectangle[2] = Math.abs( this.dragging_rectangle[2] * this.scale ); //abs to convert negative width + this.dragging_rectangle[3] = Math.abs( this.dragging_rectangle[3] * this.scale ); //abs to convert negative height + + for(var i = 0; i < nodes.length; ++i) + { + var node = nodes[i]; + node.getBounding( node_bounding ); + if(!overlapBounding( this.dragging_rectangle, node_bounding )) + continue; //out of the visible area + this.selectNode( node, true ); + } + } + this.dragging_rectangle = null; + } + else if(this.connecting_node) //dragging a connection + { + this.dirty_canvas = true; + this.dirty_bgcanvas = true; + + var node = this.graph.getNodeOnPos( e.canvasX, e.canvasY, this.visible_nodes ); + + //node below mouse + if(node) + { + if( this.connecting_output.type == LiteGraph.EVENT && this.isOverNodeBox( node, e.canvasX, e.canvasY ) ) + { + this.connecting_node.connect( this.connecting_slot, node, LiteGraph.EVENT ); + } + else + { + //slot below mouse? connect + var slot = this.isOverNodeInput(node, e.canvasX, e.canvasY); + if(slot != -1) + { + this.connecting_node.connect(this.connecting_slot, node, slot); + } + else + { //not on top of an input + var input = node.getInputInfo(0); + //auto connect + if(this.connecting_output.type == LiteGraph.EVENT) + this.connecting_node.connect( this.connecting_slot, node, LiteGraph.EVENT ); + else + if(input && !input.link && LiteGraph.isValidConnection( input.type && this.connecting_output.type ) ) + this.connecting_node.connect( this.connecting_slot, node, 0 ); + } + } + } + + this.connecting_output = null; + this.connecting_pos = null; + this.connecting_node = null; + this.connecting_slot = -1; + + }//not dragging connection + else if(this.resizing_node) + { + this.dirty_canvas = true; + this.dirty_bgcanvas = true; + this.resizing_node = null; + } + else if(this.node_dragged) //node being dragged? + { + this.dirty_canvas = true; + this.dirty_bgcanvas = true; + this.node_dragged.pos[0] = Math.round(this.node_dragged.pos[0]); + this.node_dragged.pos[1] = Math.round(this.node_dragged.pos[1]); + if(this.graph.config.align_to_grid) + this.node_dragged.alignToGrid(); + this.node_dragged = null; + } + else //no node being dragged + { + //get node over + var node = this.graph.getNodeOnPos( e.canvasX, e.canvasY, this.visible_nodes ); + if ( !node && e.click_time < 300 ) + this.deselectAllNodes(); + + this.dirty_canvas = true; + this.dragging_canvas = false; + + if( this.node_over && this.node_over.onMouseUp ) + this.node_over.onMouseUp(e, [e.canvasX - this.node_over.pos[0], e.canvasY - this.node_over.pos[1]] ); + if( this.node_capturing_input && this.node_capturing_input.onMouseUp ) + this.node_capturing_input.onMouseUp(e, [e.canvasX - this.node_capturing_input.pos[0], e.canvasY - this.node_capturing_input.pos[1]] ); + } + } + else if (e.which == 2) //middle button + { + //trace("middle"); + this.dirty_canvas = true; + this.dragging_canvas = false; + } + else if (e.which == 3) //right button + { + //trace("right"); + this.dirty_canvas = true; + this.dragging_canvas = false; + } + + /* + if((this.dirty_canvas || this.dirty_bgcanvas) && this.rendering_timer_id == null) + this.draw(); + */ + + this.graph.change(); + + e.stopPropagation(); + e.preventDefault(); + return false; +} + +/** +* Called when a mouse wheel event has to be processed +* @method processMouseWheel +**/ +LGraphCanvas.prototype.processMouseWheel = function(e) +{ + if(!this.graph || !this.allow_dragcanvas) + return; + + var delta = (e.wheelDeltaY != null ? e.wheelDeltaY : e.detail * -60); + + this.adjustMouseEvent(e); + + var zoom = this.scale; + + if (delta > 0) + zoom *= 1.1; + else if (delta < 0) + zoom *= 1/(1.1); + + this.setZoom( zoom, [ e.localX, e.localY ] ); + + /* + if(this.rendering_timer_id == null) + this.draw(); + */ + + this.graph.change(); + + e.preventDefault(); + return false; // prevent default +} + +/** +* retuns true if a position (in graph space) is on top of a node little corner box +* @method isOverNodeBox +**/ +LGraphCanvas.prototype.isOverNodeBox = function( node, canvasx, canvasy ) +{ + var title_height = LiteGraph.NODE_TITLE_HEIGHT; + if( isInsideRectangle( canvasx, canvasy, node.pos[0] + 2, node.pos[1] + 2 - title_height, title_height - 4,title_height - 4) ) + return true; + return false; +} + +/** +* retuns true if a position (in graph space) is on top of a node input slot +* @method isOverNodeInput +**/ +LGraphCanvas.prototype.isOverNodeInput = function(node, canvasx, canvasy, slot_pos ) +{ + if(node.inputs) + for(var i = 0, l = node.inputs.length; i < l; ++i) + { + var input = node.inputs[i]; + var link_pos = node.getConnectionPos(true,i); + if( isInsideRectangle(canvasx, canvasy, link_pos[0] - 10, link_pos[1] - 5, 20,10) ) + { + if(slot_pos) + { + slot_pos[0] = link_pos[0]; + slot_pos[1] = link_pos[1]; + } + return i; + } + } + return -1; +} + +/** +* process a key event +* @method processKey +**/ +LGraphCanvas.prototype.processKey = function(e) +{ + if(!this.graph) + return; + + var block_default = false; + //console.log(e); //debug + + if(e.target.localName == "input") + return; + + if(e.type == "keydown") + { + if(e.keyCode == 32) + { + this.dragging_canvas = true; + block_default = true; + } + + //select all Control A + if(e.keyCode == 65 && e.ctrlKey) + { + this.selectNodes(); + block_default = true; + } + + if(e.code == "KeyC" && (e.metaKey || e.ctrlKey) && !e.shiftKey ) //copy + { + if(this.selected_nodes) + { + this.copyToClipboard(); + block_default = true; + } + } + + if(e.code == "KeyV" && (e.metaKey || e.ctrlKey) && !e.shiftKey ) //paste + { + this.pasteFromClipboard(); + } + + //delete or backspace + if(e.keyCode == 46 || e.keyCode == 8) + { + this.deleteSelectedNodes(); + block_default = true; + } + + //collapse + //... + + //TODO + if(this.selected_nodes) + for (var i in this.selected_nodes) + if(this.selected_nodes[i].onKeyDown) + this.selected_nodes[i].onKeyDown(e); + } + else if( e.type == "keyup" ) + { + if(e.keyCode == 32) + this.dragging_canvas = false; + + if(this.selected_nodes) + for (var i in this.selected_nodes) + if(this.selected_nodes[i].onKeyUp) + this.selected_nodes[i].onKeyUp(e); + } + + this.graph.change(); + + if(block_default) + { + e.preventDefault(); + return false; + } +} + +LGraphCanvas.prototype.copyToClipboard = function() +{ + var clipboard_info = { + nodes: [], + links: [] + }; + var index = 0; + var selected_nodes_array = []; + for(var i in this.selected_nodes) + { + var node = this.selected_nodes[i]; + node._relative_id = index; + selected_nodes_array.push( node ); + index += 1; + } + + for(var i = 0; i < selected_nodes_array.length; ++i) + { + var node = selected_nodes_array[i]; + clipboard_info.nodes.push( node.clone().serialize() ); + if(node.inputs && node.inputs.length) + for(var j = 0; j < node.inputs.length; ++j) + { + var input = node.inputs[j]; + if(!input || input.link == null) + continue; + var link_info = this.graph.links[ input.link ]; + if(!link_info) + continue; + var target_node = this.graph.getNodeById( link_info.origin_id ); + if(!target_node || !this.selected_nodes[ target_node.id ] ) //improve this by allowing connections to non-selected nodes + continue; //not selected + clipboard_info.links.push([ target_node._relative_id, j, node._relative_id, link_info.target_slot ]); + } + } + localStorage.setItem( "litegrapheditor_clipboard", JSON.stringify( clipboard_info ) ); +} + +LGraphCanvas.prototype.pasteFromClipboard = function() +{ + var data = localStorage.getItem( "litegrapheditor_clipboard" ); + if(!data) + return; + + //create nodes + var clipboard_info = JSON.parse(data); + var nodes = []; + for(var i = 0; i < clipboard_info.nodes.length; ++i) + { + var node_data = clipboard_info.nodes[i]; + var node = LiteGraph.createNode( node_data.type ); + if(node) + { + node.configure(node_data); + node.pos[0] += 5; + node.pos[1] += 5; + this.graph.add( node ); + nodes.push( node ); + } + } + + //create links + for(var i = 0; i < clipboard_info.links.length; ++i) + { + var link_info = clipboard_info.links[i]; + var origin_node = nodes[ link_info[0] ]; + var target_node = nodes[ link_info[2] ]; + origin_node.connect( link_info[1], target_node, link_info[3] ); + } + + this.selectNodes( nodes ); +} + +/** +* process a item drop event on top the canvas +* @method processDrop +**/ +LGraphCanvas.prototype.processDrop = function(e) +{ + e.preventDefault(); + this.adjustMouseEvent(e); + + + var pos = [e.canvasX,e.canvasY]; + var node = this.graph.getNodeOnPos(pos[0],pos[1]); + + if(!node) + { + var r = null; + if(this.onDropItem) + r = this.onDropItem( event ); + if(!r) + this.checkDropItem(e); + return; + } + + if( node.onDropFile || node.onDropData ) + { + var files = e.dataTransfer.files; + if(files && files.length) + { + for(var i=0; i < files.length; i++) + { + var file = e.dataTransfer.files[0]; + var filename = file.name; + var ext = LGraphCanvas.getFileExtension( filename ); + //console.log(file); + + if(node.onDropFile) + node.onDropFile(file); + + if(node.onDropData) + { + //prepare reader + var reader = new FileReader(); + reader.onload = function (event) { + //console.log(event.target); + var data = event.target.result; + node.onDropData( data, filename, file ); + }; + + //read data + var type = file.type.split("/")[0]; + if(type == "text" || type == "") + reader.readAsText(file); + else if (type == "image") + reader.readAsDataURL(file); + else + reader.readAsArrayBuffer(file); + } + } + } + } + + if(node.onDropItem) + { + if( node.onDropItem( event ) ) + return true; + } + + if(this.onDropItem) + return this.onDropItem( event ); + + return false; +} + +//called if the graph doesnt have a default drop item behaviour +LGraphCanvas.prototype.checkDropItem = function(e) +{ + if(e.dataTransfer.files.length) + { + var file = e.dataTransfer.files[0]; + var ext = LGraphCanvas.getFileExtension( file.name ).toLowerCase(); + var nodetype = LiteGraph.node_types_by_file_extension[ext]; + if(nodetype) + { + var node = LiteGraph.createNode( nodetype.type ); + node.pos = [e.canvasX, e.canvasY]; + this.graph.add( node ); + if( node.onDropFile ) + node.onDropFile( file ); + } + } +} + + +LGraphCanvas.prototype.processNodeDblClicked = function(n) +{ + if(this.onShowNodePanel) + this.onShowNodePanel(n); + + if(this.onNodeDblClicked) + this.onNodeDblClicked(n); + + this.setDirty(true); +} + +LGraphCanvas.prototype.processNodeSelected = function(node,e) +{ + this.selectNode( node, e && e.shiftKey ); + if(this.onNodeSelected) + this.onNodeSelected(node); +} + +LGraphCanvas.prototype.processNodeDeselected = function(node) +{ + this.deselectNode(node); + if(this.onNodeDeselected) + this.onNodeDeselected(node); +} + +/** +* selects a given node (or adds it to the current selection) +* @method selectNode +**/ +LGraphCanvas.prototype.selectNode = function( node, add_to_current_selection ) +{ + if(node == null) + this.deselectAllNodes(); + else + this.selectNodes([node], add_to_current_selection ); +} + +/** +* selects several nodes (or adds them to the current selection) +* @method selectNodes +**/ +LGraphCanvas.prototype.selectNodes = function( nodes, add_to_current_selection ) +{ + if(!add_to_current_selection) + this.deselectAllNodes(); + + nodes = nodes || this.graph._nodes; + for(var i = 0; i < nodes.length; ++i) + { + var node = nodes[i]; + if(node.selected) + continue; + + if( !node.selected && node.onSelected ) + node.onSelected(); + node.selected = true; + this.selected_nodes[ node.id ] = node; + + if(node.inputs) + for(var i = 0; i < node.inputs.length; ++i) + this.highlighted_links[ node.inputs[i].link ] = true; + if(node.outputs) + for(var i = 0; i < node.outputs.length; ++i) + { + var out = node.outputs[i]; + if( out.links ) + for(var j = 0; j < out.links.length; ++j) + this.highlighted_links[ out.links[j] ] = true; + } + + } + + this.setDirty(true); +} + +/** +* removes a node from the current selection +* @method deselectNode +**/ +LGraphCanvas.prototype.deselectNode = function( node ) +{ + if(!node.selected) + return; + if(node.onDeselected) + node.onDeselected(); + node.selected = false; + + //remove highlighted + if(node.inputs) + for(var i = 0; i < node.inputs.length; ++i) + delete this.highlighted_links[ node.inputs[i].link ]; + if(node.outputs) + for(var i = 0; i < node.outputs.length; ++i) + { + var out = node.outputs[i]; + if( out.links ) + for(var j = 0; j < out.links.length; ++j) + delete this.highlighted_links[ out.links[j] ]; + } +} + +/** +* removes all nodes from the current selection +* @method deselectAllNodes +**/ +LGraphCanvas.prototype.deselectAllNodes = function() +{ + if(!this.graph) + return; + var nodes = this.graph._nodes; + for(var i = 0, l = nodes.length; i < l; ++i) + { + var node = nodes[i]; + if(!node.selected) + continue; + if(node.onDeselected) + node.onDeselected(); + node.selected = false; + } + this.selected_nodes = {}; + this.highlighted_links = {}; + this.setDirty(true); +} + +/** +* deletes all nodes in the current selection from the graph +* @method deleteSelectedNodes +**/ +LGraphCanvas.prototype.deleteSelectedNodes = function() +{ + for(var i in this.selected_nodes) + { + var m = this.selected_nodes[i]; + //if(m == this.node_in_panel) this.showNodePanel(null); + this.graph.remove(m); + } + this.selected_nodes = {}; + this.highlighted_links = {}; + this.setDirty(true); +} + +/** +* centers the camera on a given node +* @method centerOnNode +**/ +LGraphCanvas.prototype.centerOnNode = function(node) +{ + this.offset[0] = -node.pos[0] - node.size[0] * 0.5 + (this.canvas.width * 0.5 / this.scale); + this.offset[1] = -node.pos[1] - node.size[1] * 0.5 + (this.canvas.height * 0.5 / this.scale); + this.setDirty(true,true); +} + +/** +* adds some useful properties to a mouse event, like the position in graph coordinates +* @method adjustMouseEvent +**/ +LGraphCanvas.prototype.adjustMouseEvent = function(e) +{ + if(this.canvas) + { + var b = this.canvas.getBoundingClientRect(); + e.localX = e.pageX - b.left; + e.localY = e.pageY - b.top; + } + else + { + e.localX = e.pageX; + e.localY = e.pageY; + } + + e.deltaX = e.localX - this.last_mouse_position[0]; + e.deltaY = e.localY - this.last_mouse_position[1]; + + this.last_mouse_position[0] = e.localX; + this.last_mouse_position[1] = e.localY; + + e.canvasX = e.localX / this.scale - this.offset[0]; + e.canvasY = e.localY / this.scale - this.offset[1]; +} + +/** +* changes the zoom level of the graph (default is 1), you can pass also a place used to pivot the zoom +* @method setZoom +**/ +LGraphCanvas.prototype.setZoom = function(value, zooming_center) +{ + if(!zooming_center && this.canvas) + zooming_center = [this.canvas.width * 0.5,this.canvas.height * 0.5]; + + var center = this.convertOffsetToCanvas( zooming_center ); + + this.scale = value; + + if(this.scale > this.max_zoom) + this.scale = this.max_zoom; + else if(this.scale < this.min_zoom) + this.scale = this.min_zoom; + + var new_center = this.convertOffsetToCanvas( zooming_center ); + var delta_offset = [new_center[0] - center[0], new_center[1] - center[1]]; + + this.offset[0] += delta_offset[0]; + this.offset[1] += delta_offset[1]; + + this.dirty_canvas = true; + this.dirty_bgcanvas = true; +} + +/** +* converts a coordinate in canvas2D space to graphcanvas space (NAME IS CONFUSION, SHOULD BE THE OTHER WAY AROUND) +* @method convertOffsetToCanvas +**/ +LGraphCanvas.prototype.convertOffsetToCanvas = function( pos, out ) +{ + out = out || []; + out[0] = pos[0] / this.scale - this.offset[0]; + out[1] = pos[1] / this.scale - this.offset[1]; + return out; +} + +/** +* converts a coordinate in graphcanvas space to canvas2D space (NAME IS CONFUSION, SHOULD BE THE OTHER WAY AROUND) +* @method convertCanvasToOffset +**/ +LGraphCanvas.prototype.convertCanvasToOffset = function( pos, out ) +{ + out = out || []; + out[0] = (pos[0] + this.offset[0]) * this.scale; + out[1] = (pos[1] + this.offset[1]) * this.scale; + return out; +} + +LGraphCanvas.prototype.convertEventToCanvas = function(e) +{ + var rect = this.canvas.getBoundingClientRect(); + return this.convertOffsetToCanvas([e.pageX - rect.left,e.pageY - rect.top]); +} + +/** +* brings a node to front (above all other nodes) +* @method bringToFront +**/ +LGraphCanvas.prototype.bringToFront = function(node) +{ + var i = this.graph._nodes.indexOf(node); + if(i == -1) return; + + this.graph._nodes.splice(i,1); + this.graph._nodes.push(node); +} + +/** +* sends a node to the back (below all other nodes) +* @method sendToBack +**/ +LGraphCanvas.prototype.sendToBack = function(node) +{ + var i = this.graph._nodes.indexOf(node); + if(i == -1) return; + + this.graph._nodes.splice(i,1); + this.graph._nodes.unshift(node); +} + +/* Interaction */ + + + +/* LGraphCanvas render */ +var temp = new Float32Array(4); + +/** +* checks which nodes are visible (inside the camera area) +* @method computeVisibleNodes +**/ +LGraphCanvas.prototype.computeVisibleNodes = function( nodes, out ) +{ + var visible_nodes = out || []; + visible_nodes.length = 0; + nodes = nodes || this.graph._nodes; + for(var i = 0, l = nodes.length; i < l; ++i) + { + var n = nodes[i]; + + //skip rendering nodes in live mode + if(this.live_mode && !n.onDrawBackground && !n.onDrawForeground) + continue; + + if(!overlapBounding( this.visible_area, n.getBounding( temp ) )) + continue; //out of the visible area + + visible_nodes.push(n); + } + return visible_nodes; +} + +/** +* renders the whole canvas content, by rendering in two separated canvas, one containing the background grid and the connections, and one containing the nodes) +* @method draw +**/ +LGraphCanvas.prototype.draw = function(force_canvas, force_bgcanvas) +{ + if(!this.canvas) + return; + + //fps counting + var now = LiteGraph.getTime(); + this.render_time = (now - this.last_draw_time)*0.001; + this.last_draw_time = now; + + if(this.graph) + { + var start = [-this.offset[0], -this.offset[1] ]; + var end = [start[0] + this.canvas.width / this.scale, start[1] + this.canvas.height / this.scale]; + this.visible_area = new Float32Array([ start[0], start[1], end[0] - start[0], end[1] - start[1] ]); + } + + if(this.dirty_bgcanvas || force_bgcanvas || this.always_render_background || (this.graph && this.graph._last_trigger_time && (now - this.graph._last_trigger_time) < 1000) ) + this.drawBackCanvas(); + + if(this.dirty_canvas || force_canvas) + this.drawFrontCanvas(); + + this.fps = this.render_time ? (1.0 / this.render_time) : 0; + this.frame += 1; +} + +/** +* draws the front canvas (the one containing all the nodes) +* @method drawFrontCanvas +**/ +LGraphCanvas.prototype.drawFrontCanvas = function() +{ + this.dirty_canvas = false; + + if(!this.ctx) + this.ctx = this.bgcanvas.getContext("2d"); + var ctx = this.ctx; + if(!ctx) //maybe is using webgl... + return; + + if(ctx.start2D) + ctx.start2D(); + + var canvas = this.canvas; + + //reset in case of error + ctx.restore(); + ctx.setTransform(1, 0, 0, 1, 0, 0); + + //clip dirty area if there is one, otherwise work in full canvas + if(this.dirty_area) + { + ctx.save(); + ctx.beginPath(); + ctx.rect(this.dirty_area[0],this.dirty_area[1],this.dirty_area[2],this.dirty_area[3]); + ctx.clip(); + } + + //clear + //canvas.width = canvas.width; + if(this.clear_background) + ctx.clearRect(0,0,canvas.width, canvas.height); + + //draw bg canvas + if(this.bgcanvas == this.canvas) + this.drawBackCanvas(); + else + ctx.drawImage(this.bgcanvas,0,0); + + //rendering + if(this.onRender) + this.onRender(canvas, ctx); + + //info widget + if(this.show_info) + this.renderInfo(ctx); + + if(this.graph) + { + //apply transformations + ctx.save(); + ctx.scale(this.scale,this.scale); + ctx.translate( this.offset[0],this.offset[1] ); + + //draw nodes + var drawn_nodes = 0; + var visible_nodes = this.computeVisibleNodes( null, this.visible_nodes ); + + for (var i = 0; i < visible_nodes.length; ++i) + { + var node = visible_nodes[i]; + + //transform coords system + ctx.save(); + ctx.translate( node.pos[0], node.pos[1] ); + + //Draw + this.drawNode( node, ctx ); + drawn_nodes += 1; + + //Restore + ctx.restore(); + } + + //connections ontop? + if(this.graph.config.links_ontop) + if(!this.live_mode) + this.drawConnections(ctx); + + //current connection + if(this.connecting_pos != null) + { + ctx.lineWidth = this.connections_width; + var link_color = null; + switch( this.connecting_output.type ) + { + case LiteGraph.EVENT: link_color = LiteGraph.EVENT_LINK_COLOR; break; + default: + link_color = LiteGraph.CONNECTING_LINK_COLOR; + } + //the connection being dragged by the mouse + this.renderLink( ctx, this.connecting_pos, [this.canvas_mouse[0],this.canvas_mouse[1]], null, false, null, link_color, this.connecting_output.dir || (this.connecting_node.flags.horizontal ? LiteGraph.DOWN : LiteGraph.RIGHT), LiteGraph.CENTER ); + + ctx.beginPath(); + if( this.connecting_output.type === LiteGraph.EVENT || this.connecting_output.shape === LiteGraph.BOX_SHAPE ) + ctx.rect( (this.connecting_pos[0] - 6) + 0.5, (this.connecting_pos[1] - 5) + 0.5,14,10); + else + ctx.arc( this.connecting_pos[0], this.connecting_pos[1],4,0,Math.PI*2); + ctx.fill(); + + ctx.fillStyle = "#ffcc00"; + if(this._highlight_input) + { + ctx.beginPath(); + ctx.arc( this._highlight_input[0], this._highlight_input[1],6,0,Math.PI*2); + ctx.fill(); + } + } + + if( this.dragging_rectangle ) + { + ctx.strokeStyle = "#FFF"; + ctx.strokeRect( this.dragging_rectangle[0], this.dragging_rectangle[1], this.dragging_rectangle[2], this.dragging_rectangle[3] ); + } + + + ctx.restore(); + } + + if(this.dirty_area) + { + ctx.restore(); + //this.dirty_area = null; + } + + if(ctx.finish2D) //this is a function I use in webgl renderer + ctx.finish2D(); +} + +/** +* 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; + + ctx.save(); + ctx.translate( x, y ); + + ctx.font = "10px Arial"; + ctx.fillStyle = "#888"; + if(this.graph) + { + ctx.fillText( "T: " + this.graph.globaltime.toFixed(2)+"s",5,13*1 ); + ctx.fillText( "I: " + this.graph.iteration,5,13*2 ); + ctx.fillText( "V: " + this.graph._version,5,13*3 ); + ctx.fillText( "FPS:" + this.fps.toFixed(2),5,13*4 ); + } + else + ctx.fillText( "No graph selected",5,13*1 ); + ctx.restore(); +} + +/** +* draws the back canvas (the one containing the background and the connections) +* @method drawBackCanvas +**/ +LGraphCanvas.prototype.drawBackCanvas = function() +{ + var canvas = this.bgcanvas; + if(canvas.width != this.canvas.width || + canvas.height != this.canvas.height) + { + canvas.width = this.canvas.width; + canvas.height = this.canvas.height; + } + + if(!this.bgctx) + this.bgctx = this.bgcanvas.getContext("2d"); + var ctx = this.bgctx; + if(ctx.start) + ctx.start(); + + //clear + if(this.clear_background) + ctx.clearRect(0,0,canvas.width, canvas.height); + + if(this._graph_stack && this._graph_stack.length) + { + ctx.strokeStyle = this._graph_stack[ this._graph_stack.length - 1].bgcolor; + ctx.lineWidth = 10; + ctx.strokeRect(1,1,canvas.width-2,canvas.height-2); + ctx.lineWidth = 1; + } + + var bg_already_painted = false; + if(this.onRenderBackground) + bg_already_painted = this.onRenderBackground( canvas, ctx ); + + //reset in case of error + ctx.restore(); + ctx.setTransform(1, 0, 0, 1, 0, 0); + + if(this.graph) + { + //apply transformations + ctx.save(); + ctx.scale(this.scale,this.scale); + ctx.translate(this.offset[0],this.offset[1]); + + //render BG + if(this.background_image && this.scale > 0.5 && !bg_already_painted) + { + if (this.zoom_modify_alpha) + ctx.globalAlpha = (1.0 - 0.5 / this.scale) * this.editor_alpha; + else + ctx.globalAlpha = this.editor_alpha; + ctx.imageSmoothingEnabled = ctx.mozImageSmoothingEnabled = ctx.imageSmoothingEnabled = false; + if(!this._bg_img || this._bg_img.name != this.background_image) + { + this._bg_img = new Image(); + this._bg_img.name = this.background_image; + this._bg_img.src = this.background_image; + var that = this; + this._bg_img.onload = function() { + that.draw(true,true); + } + } + + var pattern = null; + if(this._pattern == null && this._bg_img.width > 0) + { + pattern = ctx.createPattern( this._bg_img, 'repeat' ); + this._pattern_img = this._bg_img; + this._pattern = pattern; + } + else + pattern = this._pattern; + if(pattern) + { + ctx.fillStyle = pattern; + ctx.fillRect(this.visible_area[0],this.visible_area[1],this.visible_area[2],this.visible_area[3]); + ctx.fillStyle = "transparent"; + } + + ctx.globalAlpha = 1.0; + ctx.imageSmoothingEnabled = ctx.mozImageSmoothingEnabled = ctx.imageSmoothingEnabled = true; + } + + //groups + if(this.graph._groups.length && !this.live_mode) + this.drawGroups(canvas, ctx); + + if(this.onBackgroundRender) + this.onBackgroundRender(canvas, ctx); + + //DEBUG: show clipping area + //ctx.fillStyle = "red"; + //ctx.fillRect( this.visible_area[0] + 10, this.visible_area[1] + 10, this.visible_area[2] - 20, this.visible_area[3] - 20); + + //bg + if (this.render_canvas_border) { + ctx.strokeStyle = "#235"; + ctx.strokeRect(0,0,canvas.width,canvas.height); + } + + if(this.render_connections_shadows) + { + ctx.shadowColor = "#000"; + ctx.shadowOffsetX = 0; + ctx.shadowOffsetY = 0; + ctx.shadowBlur = 6; + } + else + ctx.shadowColor = "rgba(0,0,0,0)"; + + //draw connections + if(!this.live_mode) + this.drawConnections(ctx); + + ctx.shadowColor = "rgba(0,0,0,0)"; + + //restore state + ctx.restore(); + } + + if(ctx.finish) + ctx.finish(); + + this.dirty_bgcanvas = false; + this.dirty_canvas = true; //to force to repaint the front canvas with the bgcanvas +} + +var temp_vec2 = new Float32Array(2); + +/** +* draws the given node inside the canvas +* @method drawNode +**/ +LGraphCanvas.prototype.drawNode = function(node, ctx ) +{ + var glow = false; + this.current_node = node; + + var color = node.color || node.constructor.color || LiteGraph.NODE_DEFAULT_COLOR; + var bgcolor = node.bgcolor || node.constructor.bgcolor || LiteGraph.NODE_DEFAULT_BGCOLOR; + + //shadow and glow + if (node.mouseOver) + glow = true; + + if(node.selected) + { + /* + ctx.shadowColor = "#EEEEFF";//glow ? "#AAF" : "#000"; + ctx.shadowOffsetX = 0; + ctx.shadowOffsetY = 0; + ctx.shadowBlur = 1; + */ + } + else if(this.render_shadows) + { + ctx.shadowColor = "rgba(0,0,0,0.5)"; + ctx.shadowOffsetX = 2; + ctx.shadowOffsetY = 2; + ctx.shadowBlur = 3; + } + else + ctx.shadowColor = "transparent"; + + //only render if it forces it to do it + if(this.live_mode) + { + if(!node.flags.collapsed) + { + ctx.shadowColor = "transparent"; + if(node.onDrawForeground) + node.onDrawForeground(ctx, this); + } + + return; + } + + //custom draw collapsed method + if(node.flags.collapsed && node.onDrawCollaped && node.onDrawCollapsed(ctx, this) == true) + return; + + var editor_alpha = this.editor_alpha; + ctx.globalAlpha = editor_alpha; + + //clip if required (mask) + var shape = node._shape || LiteGraph.BOX_SHAPE; + var size = temp_vec2; + temp_vec2.set( node.size ); + if( node.flags.collapsed ) + { + ctx.font = this.inner_text_font; + var title = node.getTitle ? node.getTitle() : node.title; + node._collapsed_width = Math.min( node.size[0], ctx.measureText(title).width + 40 );//LiteGraph.NODE_COLLAPSED_WIDTH; + size[0] = node._collapsed_width; + size[1] = 0; + } + + if( node.flags.clip_area ) //Start clipping + { + ctx.save(); + ctx.beginPath(); + if(shape == LiteGraph.BOX_SHAPE) + ctx.rect(0,0,size[0], size[1]); + else if (shape == LiteGraph.ROUND_SHAPE) + ctx.roundRect(0,0,size[0], size[1],10); + else if (shape == LiteGraph.CIRCLE_SHAPE) + ctx.arc(size[0] * 0.5, size[1] * 0.5, size[0] * 0.5, 0, Math.PI*2); + ctx.clip(); + } + + //draw shape + this.drawNodeShape( node, ctx, size, color, bgcolor, node.selected, node.mouseOver ); + ctx.shadowColor = "transparent"; + + //connection slots + ctx.textAlign = node.flags.horizontal ? "center" : "left"; + ctx.font = this.inner_text_font; + + var render_text = this.scale > 0.6; + + var out_slot = this.connecting_output; + ctx.lineWidth = 1; + + var max_y = 0; + + //render inputs and outputs + if(!node.flags.collapsed) + { + //input connection slots + if(node.inputs) + for(var i = 0; i < node.inputs.length; i++) + { + var slot = node.inputs[i]; + + ctx.globalAlpha = editor_alpha; + //change opacity of incompatible slots when dragging a connection + if ( this.connecting_node && LiteGraph.isValidConnection( slot.type && out_slot.type ) ) + ctx.globalAlpha = 0.4 * editor_alpha; + + ctx.fillStyle = slot.link != null ? (slot.color_on || this.default_connection_color.input_on) : (slot.color_off || this.default_connection_color.input_off); + + var pos = node.getConnectionPos( true, i ); + pos[0] -= node.pos[0]; + pos[1] -= node.pos[1]; + if( max_y < pos[1] + LiteGraph.NODE_SLOT_HEIGHT*0.5 ) + max_y = pos[1] + LiteGraph.NODE_SLOT_HEIGHT*0.5; + + ctx.beginPath(); + + if (slot.type === LiteGraph.EVENT || slot.shape === LiteGraph.BOX_SHAPE) { + ctx.rect((pos[0] - 6) + 0.5, (pos[1] - 5) + 0.5, 14, 10); + } else if (slot.shape === LiteGraph.ARROW_SHAPE) { + ctx.moveTo(pos[0] + 8, pos[1] + 0.5); + ctx.lineTo(pos[0] - 4, (pos[1] + 6) + 0.5); + ctx.lineTo(pos[0] - 4, (pos[1] - 6) + 0.5); + ctx.closePath(); + } else { + ctx.arc(pos[0], pos[1], 4, 0, Math.PI * 2); + } + + ctx.fill(); + + //render name + if(render_text) + { + var text = slot.label != null ? slot.label : slot.name; + if(text) + { + ctx.fillStyle = LiteGraph.NODE_TEXT_COLOR; + if( node.flags.horizontal || slot.dir == LiteGraph.UP ) + ctx.fillText(text,pos[0],pos[1] - 10); + else + ctx.fillText(text,pos[0] + 10,pos[1] + 5); + } + } + } + + //output connection slots + if(this.connecting_node) + ctx.globalAlpha = 0.4 * editor_alpha; + + ctx.textAlign = node.flags.horizontal ? "center" : "right"; + ctx.strokeStyle = "black"; + if(node.outputs) + for(var i = 0; i < node.outputs.length; i++) + { + var slot = node.outputs[i]; + + var pos = node.getConnectionPos(false,i); + pos[0] -= node.pos[0]; + pos[1] -= node.pos[1]; + if( max_y < pos[1] + LiteGraph.NODE_SLOT_HEIGHT*0.5) + max_y = pos[1] + LiteGraph.NODE_SLOT_HEIGHT*0.5; + + ctx.fillStyle = slot.links && slot.links.length ? (slot.color_on || this.default_connection_color.output_on) : (slot.color_off || this.default_connection_color.output_off); + ctx.beginPath(); + //ctx.rect( node.size[0] - 14,i*14,10,10); + + if (slot.type === LiteGraph.EVENT || slot.shape === LiteGraph.BOX_SHAPE) { + ctx.rect((pos[0] - 6) + 0.5,(pos[1] - 5) + 0.5,14,10); + } else if (slot.shape === LiteGraph.ARROW_SHAPE) { + ctx.moveTo(pos[0] + 8, pos[1] + 0.5); + ctx.lineTo(pos[0] - 4, (pos[1] + 6) + 0.5); + ctx.lineTo(pos[0] - 4, (pos[1] - 6) + 0.5); + ctx.closePath(); + } else { + ctx.arc(pos[0], pos[1], 4, 0, Math.PI * 2); + } + + //trigger + //if(slot.node_id != null && slot.slot == -1) + // ctx.fillStyle = "#F85"; + + //if(slot.links != null && slot.links.length) + ctx.fill(); + ctx.stroke(); + + //render output name + if(render_text) + { + var text = slot.label != null ? slot.label : slot.name; + if(text) + { + ctx.fillStyle = LiteGraph.NODE_TEXT_COLOR; + if( node.flags.horizontal || slot.dir == LiteGraph.DOWN ) + ctx.fillText(text,pos[0],pos[1] - 8); + else + ctx.fillText(text, pos[0] - 10,pos[1] + 5); + } + } + } + + ctx.textAlign = "left"; + ctx.globalAlpha = 1; + + if(node.widgets) + this.drawNodeWidgets( node, max_y, ctx, (this.node_widget && this.node_widget[0] == node) ? this.node_widget[1] : null ); + + //draw foreground + if(node.onDrawForeground) + { + //immediate gui stuff + if( node.gui_rects ) + node.gui_rects.length = 0; + node.onDrawForeground( ctx, this ); + } + } + else //if collapsed + { + if(node.inputs) + { + for(var i = 0; i < node.inputs.length; i++) + { + var slot = node.inputs[i]; + if( slot.link == null ) + continue; + ctx.fillStyle = slot.color_on || this.default_connection_color.input_on; + ctx.beginPath(); + if ( slot.type === LiteGraph.EVENT || slot.shape === LiteGraph.BOX_SHAPE) { + ctx.rect(0.5, 4 - LiteGraph.NODE_TITLE_HEIGHT + 0.5,14,LiteGraph.NODE_TITLE_HEIGHT - 8); + } else if (slot.shape === LiteGraph.ARROW_SHAPE) { + ctx.moveTo(8, LiteGraph.NODE_TITLE_HEIGHT * -0.5); + ctx.lineTo(-4, LiteGraph.NODE_TITLE_HEIGHT * -0.8); + ctx.lineTo(-4, LiteGraph.NODE_TITLE_HEIGHT * -0.2); + ctx.closePath(); + } else { + ctx.arc(0, LiteGraph.NODE_TITLE_HEIGHT * -0.5, 4, 0, Math.PI * 2); + } + ctx.fill(); + break; + } + } + + if(node.outputs) + { + for(var i = 0; i < node.outputs.length; i++) + { + var slot = node.outputs[i]; + if(!slot.links || !slot.links.length) + continue; + ctx.fillStyle = slot.color_on || this.default_connection_color.output_on; + ctx.strokeStyle = "black"; + ctx.beginPath(); + if (slot.type === LiteGraph.EVENT || slot.shape === LiteGraph.BOX_SHAPE) { + ctx.rect( node._collapsed_width - 4 + 0.5, 4 - LiteGraph.NODE_TITLE_HEIGHT + 0.5,14,LiteGraph.NODE_TITLE_HEIGHT - 8); + } else if (slot.shape === LiteGraph.ARROW_SHAPE) { + ctx.moveTo(node._collapsed_width + 6, LiteGraph.NODE_TITLE_HEIGHT * -0.5); + ctx.lineTo(node._collapsed_width - 6, LiteGraph.NODE_TITLE_HEIGHT * -0.8); + ctx.lineTo(node._collapsed_width - 6, LiteGraph.NODE_TITLE_HEIGHT * -0.2); + ctx.closePath(); + } else { + ctx.arc(node._collapsed_width, LiteGraph.NODE_TITLE_HEIGHT * -0.5, 4, 0, Math.PI * 2); + } + ctx.fill(); + ctx.stroke(); + } + } + + } + + if(node.flags.clip_area) + ctx.restore(); + + ctx.globalAlpha = 1.0; +} + +/** +* draws the shape of the given node in the canvas +* @method drawNodeShape +**/ +LGraphCanvas.prototype.drawNodeShape = function( node, ctx, size, fgcolor, bgcolor, selected, mouse_over ) +{ + //bg rect + ctx.strokeStyle = fgcolor; + ctx.fillStyle = bgcolor; + + var title_height = LiteGraph.NODE_TITLE_HEIGHT; + + //render node area depending on shape + var shape = node._shape || node.constructor.shape || LiteGraph.BOX_SHAPE; + var title_mode = node.constructor.title_mode; + + var render_title = true; + if( title_mode == LiteGraph.TRANSPARENT_TITLE ) + render_title = false; + else if( title_mode == LiteGraph.AUTOHIDE_TITLE && mouse_over) + render_title = true; + + var areax = 0; + var areay = render_title ? -title_height : 0; + var areaw = size[0]+1; + var areah = render_title ? size[1] + title_height : size[1]; + + if(!node.flags.collapsed) + { + if(shape == LiteGraph.BOX_SHAPE || this.scale < 0.5) + { + ctx.beginPath(); + ctx.rect( areax, areay, areaw, areah ); + ctx.fill(); + } + else if (shape == LiteGraph.ROUND_SHAPE || shape == LiteGraph.CARD_SHAPE) + { + ctx.beginPath(); + ctx.roundRect( areax, areay, areaw, areah, this.round_radius, shape == LiteGraph.CARD_SHAPE ? 0 : this.round_radius); + ctx.fill(); + } + else if (shape == LiteGraph.CIRCLE_SHAPE) + { + ctx.beginPath(); + ctx.arc(size[0] * 0.5, size[1] * 0.5, size[0] * 0.5, 0, Math.PI*2); + ctx.fill(); + } + } + ctx.shadowColor = "transparent"; + + //image + if (node.bgImage && node.bgImage.width) + ctx.drawImage( node.bgImage, (size[0] - node.bgImage.width) * 0.5 , (size[1] - node.bgImage.height) * 0.5); + + if(node.bgImageUrl && !node.bgImage) + node.bgImage = node.loadImage(node.bgImageUrl); + + if( node.onDrawBackground ) + { + //immediate gui stuff + if( node.gui_rects ) + node.gui_rects.length = 0; + node.onDrawBackground( ctx, this ); + } + + //title bg (remember, it is rendered ABOVE the node) + if(render_title || title_mode == LiteGraph.TRANSPARENT_TITLE ) + { + //title bar + if(title_mode != LiteGraph.TRANSPARENT_TITLE) //!node.flags.collapsed) + { + //* gradient test + if(this.use_gradients) + { + var grad = LGraphCanvas.gradients[ fgcolor ]; + if(!grad) + { + grad = LGraphCanvas.gradients[ fgcolor ] = ctx.createLinearGradient(0,0,400,0); + grad.addColorStop(0, fgcolor); + grad.addColorStop(1, "#000"); + } + ctx.fillStyle = grad; + } + else + ctx.fillStyle = fgcolor; + + var old_alpha = ctx.globalAlpha; + //ctx.globalAlpha = 0.5 * old_alpha; + ctx.beginPath(); + if(shape == LiteGraph.BOX_SHAPE || this.scale < 0.5) + { + ctx.rect(0, -title_height, size[0]+1, title_height); + ctx.fill() + //ctx.stroke(); + } + else if (shape == LiteGraph.ROUND_SHAPE || shape == LiteGraph.CARD_SHAPE) + { + ctx.roundRect(0,-title_height,size[0]+1, title_height, this.round_radius, node.flags.collapsed ? this.round_radius : 0); + ctx.fill(); + } + + /* + else if (shape == LiteGraph.CIRCLE_SHAPE) + { + ctx.beginPath(); + ctx.arc(title_height *0.5, title_height * -0.5, (title_height - 6) *0.5,0,Math.PI*2); + ctx.fill(); + } + */ + } + + //title box + ctx.fillStyle = node.boxcolor || LiteGraph.NODE_DEFAULT_BOXCOLOR; + ctx.beginPath(); + if (shape == LiteGraph.ROUND_SHAPE || shape == LiteGraph.CIRCLE_SHAPE || shape == LiteGraph.CARD_SHAPE) + ctx.arc(title_height *0.5, title_height * -0.5, (title_height - 8) *0.5,0,Math.PI*2); + else + ctx.rect(4,-title_height + 4,title_height - 8,title_height - 8); + ctx.fill(); + ctx.globalAlpha = old_alpha; + + //title text + if( this.scale > 0.5 ) + { + ctx.font = this.title_text_font; + var title = node.getTitle(); + if(title) + { + if(selected) + ctx.fillStyle = "white"; + else + ctx.fillStyle = node.constructor.title_text_color || this.node_title_color; + if( node.flags.collapsed ) + { + ctx.textAlign = "center"; + var measure = ctx.measureText(title); + ctx.fillText( title, title_height + measure.width * 0.5, -title_height * 0.2 ); + ctx.textAlign = "left"; + } + else + { + ctx.textAlign = "left"; + ctx.fillText( title, title_height, -title_height * 0.2 ); + } + } + } + } + + //render selection marker + if(selected) + { + if( title_mode == LiteGraph.TRANSPARENT_TITLE ) + { + areay -= title_height; + areah += title_height; + } + ctx.lineWidth = 1; + ctx.globalAlpha = 0.8; + ctx.beginPath(); + if(shape == LiteGraph.BOX_SHAPE) + ctx.rect(-6 + areax,-6 + areay, 12 + areaw, 12 + areah ); + else if (shape == LiteGraph.ROUND_SHAPE || (shape == LiteGraph.CARD_SHAPE && node.flags.collapsed) ) + ctx.roundRect(-6 + areax,-6 + areay, 12 + areaw, 12 + areah , this.round_radius * 2); + else if (shape == LiteGraph.CARD_SHAPE) + ctx.roundRect(-6 + areax,-6 + areay, 12 + areaw, 12 + areah , this.round_radius * 2, 2); + else if (shape == LiteGraph.CIRCLE_SHAPE) + ctx.arc(size[0] * 0.5, size[1] * 0.5, size[0] * 0.5 + 6, 0, Math.PI*2); + ctx.strokeStyle = "#FFF"; + ctx.stroke(); + ctx.strokeStyle = fgcolor; + ctx.globalAlpha = 1; + } +} + +/** +* draws every connection visible in the canvas +* OPTIMIZE THIS: precatch connections position instead of recomputing them every time +* @method drawConnections +**/ +LGraphCanvas.prototype.drawConnections = function(ctx) +{ + var now = LiteGraph.getTime(); + + //draw connections + ctx.lineWidth = this.connections_width; + + ctx.fillStyle = "#AAA"; + ctx.strokeStyle = "#AAA"; + ctx.globalAlpha = this.editor_alpha; + //for every node + for (var n = 0, l = this.graph._nodes.length; n < l; ++n) + { + var node = this.graph._nodes[n]; + //for every input (we render just inputs because it is easier as every slot can only have one input) + if(node.inputs && node.inputs.length) + for(var i = 0; i < node.inputs.length; ++i) + { + var input = node.inputs[i]; + if(!input || input.link == null) + continue; + var link_id = input.link; + var link = this.graph.links[ link_id ]; + if(!link) + continue; + + var start_node = this.graph.getNodeById( link.origin_id ); + if(start_node == null) continue; + var start_node_slot = link.origin_slot; + var start_node_slotpos = null; + + if(start_node_slot == -1) + start_node_slotpos = [start_node.pos[0] + 10, start_node.pos[1] + 10]; + else + start_node_slotpos = start_node.getConnectionPos(false, start_node_slot); + var end_node_slotpos = node.getConnectionPos(true,i); + var start_slot = start_node.outputs[start_node_slot]; + var end_slot = node.inputs[i]; + var start_dir = start_slot.dir || (start_node.flags.horizontal ? LiteGraph.DOWN : LiteGraph.RIGHT); + var end_dir = end_slot.dir || (node.flags.horizontal ? LiteGraph.UP : LiteGraph.LEFT); + + this.renderLink( ctx, start_node_slotpos, end_node_slotpos, link, false, 0, null, start_dir, end_dir ); + + //event triggered rendered on top + if(link && link._last_time && (now - link._last_time) < 1000 ) + { + var f = 2.0 - (now - link._last_time) * 0.002; + var color = "rgba(255,255,255, " + f.toFixed(2) + ")"; + this.renderLink( ctx, start_node_slotpos, end_node_slotpos, link, true, f, color, start_dir, end_dir ); + } + } + } + ctx.globalAlpha = 1; +} + +/** +* draws a link between two points +* @method renderLink +**/ +LGraphCanvas.prototype.renderLink = function( ctx, a, b, link, skip_border, flow, color, start_dir, end_dir ) +{ + if(!this.highquality_render) + { + ctx.beginPath(); + ctx.moveTo(a[0],a[1]); + ctx.lineTo(b[0],b[1]); + ctx.stroke(); + return; + } + + start_dir = start_dir || LiteGraph.RIGHT; + end_dir = end_dir || LiteGraph.LEFT; + + var dist = distance(a,b); + + if(this.render_connections_border && this.scale > 0.6) + ctx.lineWidth = this.connections_width + 4; + + //choose color + if( !color && link ) + color = LGraphCanvas.link_type_colors[ link.type ]; + if( !color ) + color = this.default_link_color; + + if( link != null && this.highlighted_links[ link.id ] ) + color = "#FFF"; + + //begin line shape + ctx.beginPath(); + + if(this.render_curved_connections) //splines + { + ctx.moveTo(a[0],a[1]); + var start_offset_x = 0; + var start_offset_y = 0; + var end_offset_x = 0; + var end_offset_y = 0; + switch(start_dir) + { + case LiteGraph.LEFT: start_offset_x = dist*-0.25; break; + case LiteGraph.RIGHT: start_offset_x = dist*0.25; break; + case LiteGraph.UP: start_offset_y = dist*-0.25; break; + case LiteGraph.DOWN: start_offset_y = dist*0.25; break; + } + switch(end_dir) + { + case LiteGraph.LEFT: end_offset_x = dist*-0.25; break; + case LiteGraph.RIGHT: end_offset_x = dist*0.25; break; + case LiteGraph.UP: end_offset_y = dist*-0.25; break; + case LiteGraph.DOWN: end_offset_y = dist*0.25; break; + } + ctx.bezierCurveTo(a[0] + start_offset_x, a[1] + start_offset_y, + b[0] + end_offset_x , b[1] + end_offset_y, + b[0], b[1] ); + } + else //lines + { + ctx.moveTo(a[0]+10,a[1]); + ctx.lineTo(((a[0]+10) + (b[0]-10))*0.5,a[1]); + ctx.lineTo(((a[0]+10) + (b[0]-10))*0.5,b[1]); + ctx.lineTo(b[0]-10,b[1]); + } + + //rendering the outline of the connection can be a little bit slow + if(this.render_connections_border && this.scale > 0.6 && !skip_border) + { + ctx.strokeStyle = "rgba(0,0,0,0.5)"; + ctx.stroke(); + } + + ctx.lineWidth = this.connections_width; + ctx.fillStyle = ctx.strokeStyle = color; + ctx.stroke(); + //end line shape + + //render arrow in the middle + if( this.render_connection_arrows && this.scale >= 0.6 ) + { + //render arrow + if(this.render_connection_arrows && this.scale > 0.6) + { + //compute two points in the connection + var pos = this.computeConnectionPoint(a, b, 0.5, start_dir, end_dir); + var pos2 = this.computeConnectionPoint(a, b, 0.51, start_dir, end_dir); + + //compute the angle between them so the arrow points in the right direction + var angle = 0; + if(this.render_curved_connections) + angle = -Math.atan2( pos2[0] - pos[0], pos2[1] - pos[1]); + else + angle = b[1] > a[1] ? 0 : Math.PI; + + //render arrow + ctx.save(); + ctx.translate(pos[0],pos[1]); + ctx.rotate(angle); + ctx.beginPath(); + ctx.moveTo(-5,-5); + ctx.lineTo(0,+5); + ctx.lineTo(+5,-5); + ctx.fill(); + ctx.restore(); + } + } + + //render flowing points + if(flow) + { + for(var i = 0; i < 5; ++i) + { + var f = (LiteGraph.getTime() * 0.001 + (i * 0.2)) % 1; + var pos = this.computeConnectionPoint(a,b,f, start_dir, end_dir); + ctx.beginPath(); + ctx.arc(pos[0],pos[1],5,0,2*Math.PI); + ctx.fill(); + } + } +} + +LGraphCanvas.prototype.computeConnectionPoint = function(a,b,t,start_dir,end_dir) +{ + start_dir = start_dir || LiteGraph.RIGHT; + end_dir = end_dir || LiteGraph.LEFT; + + var dist = distance(a,b); + var p0 = a; + var p1 = [ a[0], a[1] ]; + var p2 = [ b[0], b[1] ]; + var p3 = b; + + switch(start_dir) + { + case LiteGraph.LEFT: p1[0] += dist*-0.25; break; + case LiteGraph.RIGHT: p1[0] += dist*0.25; break; + case LiteGraph.UP: p1[1] += dist*-0.25; break; + case LiteGraph.DOWN: p1[1] += dist*0.25; break; + } + switch(end_dir) + { + case LiteGraph.LEFT: p2[0] += dist*-0.25; break; + case LiteGraph.RIGHT: p2[0] += dist*0.25; break; + case LiteGraph.UP: p2[1] += dist*-0.25; break; + case LiteGraph.DOWN: p2[1] += dist*0.25; break; + } + + var c1 = (1-t)*(1-t)*(1-t); + var c2 = 3*((1-t)*(1-t))*t; + var c3 = 3*(1-t)*(t*t); + var c4 = t*t*t; + + var x = c1*p0[0] + c2*p1[0] + c3*p2[0] + c4*p3[0]; + var y = c1*p0[1] + c2*p1[1] + c3*p2[1] + c4*p3[1]; + return [x,y]; +} + +/** +* draws the widgets stored inside a node +* @method drawNodeWidgets +**/ +LGraphCanvas.prototype.drawNodeWidgets = function( node, posY, ctx, active_widget ) +{ + if(!node.widgets || !node.widgets.length) + return 0; + var width = node.size[0]; + var widgets = node.widgets; + posY += 2; + var H = LiteGraph.NODE_WIDGET_HEIGHT; + var show_text = this.scale > 0.5; + + for(var i = 0; i < widgets.length; ++i) + { + var w = widgets[i]; + var y = posY; + if(w.y) + y = w.y; + w.last_y = y; + ctx.strokeStyle = "#AAA"; + ctx.fillStyle = "#222"; + ctx.textAlign = "left"; + + switch( w.type ) + { + case "button": + if(w.clicked) + { + ctx.fillStyle = "#AAA"; + w.clicked = false; + this.dirty_canvas = true; + } + ctx.fillRect(10,y,width-20,H); + ctx.strokeRect(10,y,width-20,H); + if(show_text) + { + ctx.textAlign = "center"; + ctx.fillStyle = "#AAA"; + ctx.fillText( w.name, width*0.5, y + H*0.7 ); + } + break; + case "slider": + ctx.fillStyle = "#111"; + ctx.fillRect(10,y,width-20,H); + var range = w.options.max - w.options.min; + var nvalue = (w.value - w.options.min) / range; + ctx.fillStyle = active_widget == w ? "#89A" : "#678"; + ctx.fillRect(10,y,nvalue*(width-20),H); + ctx.strokeRect(10,y,width-20,H); + if(show_text) + { + ctx.textAlign = "center"; + ctx.fillStyle = "#DDD"; + ctx.fillText( w.name + " " + Number(w.value).toFixed(3), width*0.5, y + H*0.7 ); + } + break; + case "number": + case "combo": + ctx.textAlign = "left"; + ctx.strokeStyle = "#AAA"; + ctx.fillStyle = "#111"; + ctx.beginPath(); + ctx.roundRect( 10, posY, width - 20, H,H*0.5 ); + ctx.fill(); + ctx.stroke(); + ctx.fillStyle = "#AAA"; + ctx.beginPath(); + ctx.moveTo( 26, posY + 5 ); + ctx.lineTo( 16, posY + H*0.5 ); + ctx.lineTo( 26, posY + H - 5 ); + ctx.moveTo( width - 26, posY + 5 ); + ctx.lineTo( width - 16, posY + H*0.5 ); + ctx.lineTo( width - 26, posY + H - 5 ); + ctx.fill(); + if(show_text) + { + ctx.fillStyle = "#999"; + ctx.fillText( w.name, 30, y + H*0.7 ); + ctx.fillStyle = "#DDD"; + ctx.textAlign = "right"; + if(w.type == "number") + ctx.fillText( Number(w.value).toFixed( w.options.precision !== undefined ? w.options.precision : 3), width - 40, y + H*0.7 ); + else + ctx.fillText( w.value, width - 40, y + H*0.7 ); + } + break; + default: + break; + } + posY += H + 4; + } + ctx.textAlign = "left"; +} + +/** +* process an event on widgets +* @method processNodeWidgets +**/ +LGraphCanvas.prototype.processNodeWidgets = function( node, pos, event, active_widget ) +{ + if(!node.widgets || !node.widgets.length) + return null; + + var x = pos[0] - node.pos[0]; + var y = pos[1] - node.pos[1]; + var width = node.size[0]; + var that = this; + + for(var i = 0; i < node.widgets.length; ++i) + { + var w = node.widgets[i]; + if( w == active_widget || (x > 6 && x < (width - 12) && y > w.last_y && y < (w.last_y + LiteGraph.NODE_WIDGET_HEIGHT)) ) + { + //inside widget + switch( w.type ) + { + case "button": + if(w.callback) + setTimeout( function(){ w.callback( w, that, node, pos ); }, 20 ); + w.clicked = true; + this.dirty_canvas = true; + break; + case "slider": + var range = w.options.max - w.options.min; + var nvalue = Math.clamp( (x - 10) / (width - 20), 0, 1); + w.value = w.options.min + (w.options.max - w.options.min) * nvalue; + if(w.callback) + setTimeout( function(){ w.callback( w.value, that, node, pos ); }, 20 ); + this.dirty_canvas = true; + break; + case "number": + case "combo": + if(event.type == "mousemove" && w.type == "number") + { + w.value += (event.deltaX * 0.1) * (w.options.step || 1); + if(w.options.min != null && w.value < w.options.min) + w.value = w.options.min; + if(w.options.max != null && w.value > w.options.max) + w.value = w.options.max; + } + else if( event.type == "mousedown" ) + { + var delta = ( x < 40 ? -1 : ( x > width - 40 ? 1 : 0) ); + if (w.type == "number") + { + w.value += delta * 0.1 * (w.options.step || 1); + if(w.options.min != null && w.value < w.options.min) + w.value = w.options.min; + if(w.options.max != null && w.value > w.options.max) + w.value = w.options.max; + } + else if(delta) + { + var index = w.options.values.indexOf( w.value ) + delta; + if( index >= w.options.values.length ) + index = 0; + if( index < 0 ) + index = w.options.values.length - 1; + w.value = w.options.values[ index ]; + } + } + if(w.callback) + setTimeout( function(){ w.callback( w.value, that, node, pos ); }, 20 ); + this.dirty_canvas = true; + break; + } + + return w; + } + } + return null; +} + +/** +* draws every group area in the background +* @method drawGroups +**/ +LGraphCanvas.prototype.drawGroups = function(canvas, ctx) +{ + if(!this.graph) + return; + + var groups = this.graph._groups; + + ctx.save(); + ctx.globalAlpha = 0.5; + ctx.font = "24px Arial"; + + for(var i = 0; i < groups.length; ++i) + { + var group = groups[i]; + + if(!overlapBounding( this.visible_area, group._bounding )) + continue; //out of the visible area + + ctx.fillStyle = group.color || "#335"; + ctx.strokeStyle = group.color || "#335"; + var pos = group._pos; + var size = group._size; + ctx.globalAlpha = 0.25; + ctx.beginPath(); + ctx.rect( pos[0] + 0.5, pos[1] + 0.5, size[0], size[1] ); + ctx.fill(); + ctx.globalAlpha = 1; + ctx.stroke(); + + ctx.beginPath(); + ctx.moveTo( pos[0] + size[0], pos[1] + size[1] ); + ctx.lineTo( pos[0] + size[0] - 10, pos[1] + size[1] ); + ctx.lineTo( pos[0] + size[0], pos[1] + size[1] - 10 ); + ctx.fill(); + + ctx.fillText( group.title, pos[0] + 4, pos[1] + 24 ); + } + + ctx.restore(); +} + +/** +* resizes the canvas to a given size, if no size is passed, then it tries to fill the parentNode +* @method resize +**/ +LGraphCanvas.prototype.resize = function(width, height) +{ + if(!width && !height) + { + var parent = this.canvas.parentNode; + width = parent.offsetWidth; + height = parent.offsetHeight; + } + + if(this.canvas.width == width && this.canvas.height == height) + return; + + this.canvas.width = width; + this.canvas.height = height; + this.bgcanvas.width = this.canvas.width; + this.bgcanvas.height = this.canvas.height; + this.setDirty(true,true); +} + +/** +* switches to live mode (node shapes are not rendered, only the content) +* this feature was designed when graphs where meant to create user interfaces +* @method switchLiveMode +**/ +LGraphCanvas.prototype.switchLiveMode = function(transition) +{ + if(!transition) + { + this.live_mode = !this.live_mode; + this.dirty_canvas = true; + this.dirty_bgcanvas = true; + return; + } + + var self = this; + var delta = this.live_mode ? 1.1 : 0.9; + if(this.live_mode) + { + this.live_mode = false; + this.editor_alpha = 0.1; + } + + var t = setInterval(function() { + self.editor_alpha *= delta; + self.dirty_canvas = true; + self.dirty_bgcanvas = true; + + if(delta < 1 && self.editor_alpha < 0.01) + { + clearInterval(t); + if(delta < 1) + self.live_mode = true; + } + if(delta > 1 && self.editor_alpha > 0.99) + { + clearInterval(t); + self.editor_alpha = 1; + } + },1); +} + +LGraphCanvas.prototype.onNodeSelectionChange = function(node) +{ + return; //disabled +} + +LGraphCanvas.prototype.touchHandler = function(event) +{ + //alert("foo"); + var touches = event.changedTouches, + first = touches[0], + type = ""; + + switch(event.type) + { + case "touchstart": type = "mousedown"; break; + case "touchmove": type = "mousemove"; break; + case "touchend": type = "mouseup"; break; + default: return; + } + + //initMouseEvent(type, canBubble, cancelable, view, clickCount, + // screenX, screenY, clientX, clientY, ctrlKey, + // altKey, shiftKey, metaKey, button, relatedTarget); + + var window = this.getCanvasWindow(); + var document = window.document; + + var simulatedEvent = document.createEvent("MouseEvent"); + simulatedEvent.initMouseEvent(type, true, true, window, 1, + first.screenX, first.screenY, + first.clientX, first.clientY, false, + false, false, false, 0/*left*/, null); + first.target.dispatchEvent(simulatedEvent); + event.preventDefault(); +} + +/* CONTEXT MENU ********************/ + +LGraphCanvas.onGroupAdd = function(info,entry,mouse_event) +{ + var canvas = LGraphCanvas.active_canvas; + var ref_window = canvas.getCanvasWindow(); + + var group = new LiteGraph.LGraphGroup(); + group.pos = canvas.convertEventToCanvas( mouse_event ); + canvas.graph.add( group ); +} + +LGraphCanvas.onMenuAdd = function( node, options, e, prev_menu ) +{ + var canvas = LGraphCanvas.active_canvas; + var ref_window = canvas.getCanvasWindow(); + + var values = LiteGraph.getNodeTypesCategories(); + var entries = []; + for(var i in values) + if(values[i]) + entries.push({ value: values[i], content: values[i], has_submenu: true }); + + var menu = new LiteGraph.ContextMenu( entries, { event: e, callback: inner_clicked, parentMenu: prev_menu }, ref_window); + + function inner_clicked( v, option, e ) + { + var category = v.value; + var node_types = LiteGraph.getNodeTypesInCategory( category, canvas.filter ); + var values = []; + for(var i in node_types) + values.push( { content: node_types[i].title, value: node_types[i].type }); + + new LiteGraph.ContextMenu( values, {event: e, callback: inner_create, parentMenu: menu }, ref_window); + return false; + } + + function inner_create( v, e ) + { + var first_event = prev_menu.getFirstEvent(); + var node = LiteGraph.createNode( v.value ); + if(node) + { + node.pos = canvas.convertEventToCanvas( first_event ); + canvas.graph.add( node ); + } + } + + return false; +} + +LGraphCanvas.onMenuCollapseAll = function() +{ + +} + + +LGraphCanvas.onMenuNodeEdit = function() +{ + +} + +LGraphCanvas.showMenuNodeOptionalInputs = function( v, options, e, prev_menu, node ) +{ + if(!node) + return; + + var that = this; + var canvas = LGraphCanvas.active_canvas; + var ref_window = canvas.getCanvasWindow(); + + var options = node.optional_inputs; + if(node.onGetInputs) + options = node.onGetInputs(); + + var entries = []; + if(options) + for (var i in options) + { + var entry = options[i]; + if(!entry) + { + entries.push(null); + continue; + } + var label = entry[0]; + if(entry[2] && entry[2].label) + label = entry[2].label; + var data = {content: label, value: entry}; + if(entry[1] == LiteGraph.ACTION) + data.className = "event"; + entries.push(data); + } + + if(this.onMenuNodeInputs) + entries = this.onMenuNodeInputs( entries ); + + if(!entries.length) + return; + + var menu = new LiteGraph.ContextMenu(entries, { event: e, callback: inner_clicked, parentMenu: prev_menu, node: node }, ref_window); + + function inner_clicked(v, e, prev) + { + if(!node) + return; + + if(v.callback) + v.callback.call( that, node, v, e, prev ); + + if(v.value) + { + node.addInput(v.value[0],v.value[1], v.value[2]); + node.setDirtyCanvas(true,true); + } + } + + return false; +} + +LGraphCanvas.showMenuNodeOptionalOutputs = function( v, options, e, prev_menu, node ) +{ + if(!node) + return; + + var that = this; + var canvas = LGraphCanvas.active_canvas; + var ref_window = canvas.getCanvasWindow(); + + var options = node.optional_outputs; + if(node.onGetOutputs) + options = node.onGetOutputs(); + + var entries = []; + if(options) + for (var i in options) + { + var entry = options[i]; + if(!entry) //separator? + { + entries.push(null); + continue; + } + + if(node.flags && node.flags.skip_repeated_outputs && node.findOutputSlot(entry[0]) != -1) + continue; //skip the ones already on + var label = entry[0]; + if(entry[2] && entry[2].label) + label = entry[2].label; + var data = {content: label, value: entry}; + if(entry[1] == LiteGraph.EVENT) + data.className = "event"; + entries.push(data); + } + + if(this.onMenuNodeOutputs) + entries = this.onMenuNodeOutputs( entries ); + + if(!entries.length) + return; + + var menu = new LiteGraph.ContextMenu(entries, {event: e, callback: inner_clicked, parentMenu: prev_menu, node: node }, ref_window); + + function inner_clicked( v, e, prev ) + { + if(!node) + return; + + if(v.callback) + v.callback.call( that, node, v, e, prev ); + + if(!v.value) + return; + + var value = v.value[1]; + + if(value && (value.constructor === Object || value.constructor === Array)) //submenu why? + { + var entries = []; + for(var i in value) + entries.push({ content: i, value: value[i]}); + new LiteGraph.ContextMenu( entries, { event: e, callback: inner_clicked, parentMenu: prev_menu, node: node }); + return false; + } + else + { + node.addOutput( v.value[0], v.value[1], v.value[2]); + node.setDirtyCanvas(true,true); + } + + } + + return false; +} + +LGraphCanvas.onShowMenuNodeProperties = function( value, options, e, prev_menu, node ) +{ + if(!node || !node.properties) + return; + + var that = this; + var canvas = LGraphCanvas.active_canvas; + var ref_window = canvas.getCanvasWindow(); + + var entries = []; + for (var i in node.properties) + { + var value = node.properties[i] !== undefined ? node.properties[i] : " "; + //value could contain invalid html characters, clean that + value = LGraphCanvas.decodeHTML(value); + entries.push({content: "" + i + "" + "" + value + "", value: i}); + } + if(!entries.length) + return; + + var menu = new LiteGraph.ContextMenu(entries, {event: e, callback: inner_clicked, parentMenu: prev_menu, allow_html: true, node: node },ref_window); + + function inner_clicked( v, options, e, prev ) + { + if(!node) + return; + var rect = this.getBoundingClientRect(); + canvas.showEditPropertyValue( node, v.value, { position: [rect.left, rect.top] }); + } + + return false; +} + +LGraphCanvas.decodeHTML = function( str ) +{ + var e = document.createElement("div"); + e.innerText = str; + return e.innerHTML; +} + +LGraphCanvas.onResizeNode = function( value, options, e, menu, node ) +{ + if(!node) + return; + node.size = node.computeSize(); + node.setDirtyCanvas(true,true); +} + + +LGraphCanvas.onShowTitleEditor = function( value, options, e, menu, node ) +{ + var input_html = ""; + + var dialog = document.createElement("div"); + dialog.className = "graphdialog"; + dialog.innerHTML = "Title"; + var input = dialog.querySelector("input"); + if(input) + { + input.value = node.title; + input.addEventListener("keydown", function(e){ + if(e.keyCode != 13) + return; + inner(); + e.preventDefault(); + e.stopPropagation(); + }); + } + + var graphcanvas = LGraphCanvas.active_canvas; + var canvas = graphcanvas.canvas; + + var rect = canvas.getBoundingClientRect(); + var offsetx = -20; + var offsety = -20; + if(rect) + { + offsetx -= rect.left; + offsety -= rect.top; + } + + if( event ) + { + dialog.style.left = (event.pageX + offsetx) + "px"; + dialog.style.top = (event.pageY + offsety)+ "px"; + } + else + { + dialog.style.left = (canvas.width * 0.5 + offsetx) + "px"; + dialog.style.top = (canvas.height * 0.5 + offsety) + "px"; + } + + var button = dialog.querySelector("button"); + button.addEventListener("click", inner ); + canvas.parentNode.appendChild( dialog ); + + function inner() + { + setValue( input.value ); + } + + function setValue(value) + { + node.title = value; + dialog.parentNode.removeChild( dialog ); + node.setDirtyCanvas(true,true); + } +} + +LGraphCanvas.prototype.showSearchBox = function(event) +{ + var that = this; + var input_html = ""; + + var dialog = document.createElement("div"); + dialog.className = "litegraph litesearchbox"; + dialog.innerHTML = "Search
"; + dialog.close = function() + { + that.search_box = null; + dialog.parentNode.removeChild( dialog ); + } + + dialog.addEventListener("mouseleave",function(e){ + dialog.close(); + }); + + if(that.search_box) + that.search_box.close(); + that.search_box = dialog; + + var helper = dialog.querySelector(".helper"); + + var first = null; + var timeout = null; + var selected = null; + + var input = dialog.querySelector("input"); + if(input) + { + input.addEventListener("keydown", function(e){ + + if(e.keyCode == 38) //UP + changeSelection(false); + else if(e.keyCode == 40) //DOWN + changeSelection(true); + else if(e.keyCode == 27) //ESC + dialog.close(); + else if(e.keyCode == 13) + { + if(selected) + select( selected.innerHTML ) + else if(first) + select(first); + else + dialog.close(); + } + else + { + if(timeout) + clearInterval(timeout); + timeout = setTimeout( refreshHelper, 10 ); + return; + } + e.preventDefault(); + e.stopPropagation(); + }); + } + + var graphcanvas = LGraphCanvas.active_canvas; + var canvas = graphcanvas.canvas; + + var rect = canvas.getBoundingClientRect(); + var offsetx = -20; + var offsety = -20; + if(rect) + { + offsetx -= rect.left; + offsety -= rect.top; + } + + if( event ) + { + dialog.style.left = (event.pageX + offsetx) + "px"; + dialog.style.top = (event.pageY + offsety)+ "px"; + } + else + { + dialog.style.left = (canvas.width * 0.5 + offsetx) + "px"; + dialog.style.top = (canvas.height * 0.5 + offsety) + "px"; + } + + canvas.parentNode.appendChild( dialog ); + input.focus(); + + function select( name ) + { + if(name) + { + if( that.onSearchBoxSelection ) + that.onSearchBoxSelection( name, event, graphcanvas ); + else + { + var node = LiteGraph.createNode( name ); + if(node) + { + node.pos = graphcanvas.convertEventToCanvas( event ); + graphcanvas.graph.add( node ); + } + } + } + + dialog.close(); + } + + function changeSelection( forward ) + { + var prev = selected; + if(selected) + selected.classList.remove("selected"); + if(!selected) + selected = forward ? helper.childNodes[0] : helper.childNodes[ helper.childNodes.length ]; + else + { + selected = forward ? selected.nextSibling : selected.previousSibling; + if(!selected) + selected = prev; + } + if(!selected) + return; + selected.classList.add("selected"); + selected.scrollIntoView(); + } + + function refreshHelper() + { + timeout = null; + var str = input.value; + first = null; + helper.innerHTML = ""; + if(!str) + return; + + if( that.onSearchBox ) + that.onSearchBox( help, str, graphcanvas ); + else + for( var i in LiteGraph.registered_node_types ) + if(i.indexOf(str) != -1) + { + var help = document.createElement("div"); + if(!first) first = i; + help.innerText = i; + help.className = "litegraph lite-search-item"; + help.addEventListener("click", function(e){ + select( this.innerText ); + }); + helper.appendChild(help); + } + } + + return dialog; +} + +LGraphCanvas.prototype.showEditPropertyValue = function( node, property, options ) +{ + if(!node || node.properties[ property ] === undefined ) + return; + + options = options || {}; + var that = this; + + var type = "string"; + + if(node.properties[ property ] !== null) + type = typeof(node.properties[ property ]); + + //for arrays + if(type == "object") + { + if( node.properties[ property ].length ) + type = "array"; + } + + var info = null; + if(node.getPropertyInfo) + info = node.getPropertyInfo(property); + if(node.properties_info) + { + for(var i = 0; i < node.properties_info.length; ++i) + { + if( node.properties_info[i].name == property ) + { + info = node.properties_info[i]; + break; + } + } + } + + if(info !== undefined && info !== null && info.type ) + type = info.type; + + var input_html = ""; + + if(type == "string" || type == "number" || type == "array") + input_html = ""; + else if(type == "enum" && info.values) + { + input_html = ""; + } + else if(type == "boolean") + { + input_html = ""; + } + else + { + console.warn("unknown type: " + type ); + return; + } + + var dialog = this.createDialog( "" + property + ""+input_html+"" , options ); + + if(type == "enum" && info.values) + { + var input = dialog.querySelector("select"); + input.addEventListener("change", function(e){ + setValue( e.target.value ); + //var index = e.target.value; + //setValue( e.options[e.selectedIndex].value ); + }); + } + else if(type == "boolean") + { + var input = dialog.querySelector("input"); + if(input) + { + input.addEventListener("click", function(e){ + setValue( !!input.checked ); + }); + } + } + else + { + var input = dialog.querySelector("input"); + if(input) + { + input.value = node.properties[ property ] !== undefined ? node.properties[ property ] : ""; + input.addEventListener("keydown", function(e){ + if(e.keyCode != 13) + return; + inner(); + e.preventDefault(); + e.stopPropagation(); + }); + } + } + + var button = dialog.querySelector("button"); + button.addEventListener("click", inner ); + + function inner() + { + setValue( input.value ); + } + + function setValue(value) + { + if(typeof( node.properties[ property ] ) == "number") + value = Number(value); + if(type == "array") + value = value.split(",").map(Number); + node.properties[ property ] = value; + if(node._graph) + node._graph._version++; + if(node.onPropertyChanged) + node.onPropertyChanged( property, value ); + dialog.close(); + node.setDirtyCanvas(true,true); + } +} + +LGraphCanvas.prototype.createDialog = function( html, options ) +{ + options = options || {}; + + var dialog = document.createElement("div"); + dialog.className = "graphdialog"; + dialog.innerHTML = html; + + var rect = this.canvas.getBoundingClientRect(); + var offsetx = -20; + var offsety = -20; + if(rect) + { + offsetx -= rect.left; + offsety -= rect.top; + } + + if( options.position ) + { + offsetx += options.position[0]; + offsety += options.position[1]; + } + else if( options.event ) + { + offsetx += options.event.pageX; + offsety += options.event.pageY; + } + else //centered + { + offsetx += this.canvas.width * 0.5; + offsety += this.canvas.height * 0.5; + } + + dialog.style.left = offsetx + "px"; + dialog.style.top = offsety + "px"; + + this.canvas.parentNode.appendChild( dialog ); + + dialog.close = function() + { + if(this.parentNode) + this.parentNode.removeChild( this ); + } + + return dialog; +} + +LGraphCanvas.onMenuNodeCollapse = function( value, options, e, menu, node ) +{ + node.collapse(); +} + +LGraphCanvas.onMenuNodePin = function( value, options, e, menu, node ) +{ + node.pin(); +} + +LGraphCanvas.onMenuNodeMode = function( value, options, e, menu, node ) +{ + new LiteGraph.ContextMenu(["Always","On Event","On Trigger","Never"], {event: e, callback: inner_clicked, parentMenu: menu, node: node }); + + function inner_clicked(v) + { + if(!node) + return; + switch(v) + { + case "On Event": node.mode = LiteGraph.ON_EVENT; break; + case "On Trigger": node.mode = LiteGraph.ON_TRIGGER; break; + case "Never": node.mode = LiteGraph.NEVER; break; + case "Always": + default: + node.mode = LiteGraph.ALWAYS; break; + } + } + + return false; +} + +LGraphCanvas.onMenuNodeColors = function( value, options, e, menu, node ) +{ + if(!node) + throw("no node for color"); + + var values = []; + values.push({ value:null, content:"No color" }); + + for(var i in LGraphCanvas.node_colors) + { + var color = LGraphCanvas.node_colors[i]; + var value = { value:i, content:""+i+"" }; + values.push(value); + } + new LiteGraph.ContextMenu( values, { event: e, callback: inner_clicked, parentMenu: menu, node: node }); + + function inner_clicked(v) + { + if(!node) + return; + + var color = v.value ? LGraphCanvas.node_colors[ v.value ] : null; + if(color) + { + if(node.constructor === LiteGraph.LGraphGroup) + node.color = color.groupcolor; + else + { + node.color = color.color; + node.bgcolor = color.bgcolor; + } + } + else + { + delete node.color; + delete node.bgcolor; + } + node.setDirtyCanvas(true,true); + } + + return false; +} + +LGraphCanvas.onMenuNodeShapes = function( value, options, e, menu, node ) +{ + if(!node) + throw("no node passed"); + + new LiteGraph.ContextMenu( LiteGraph.VALID_SHAPES, { event: e, callback: inner_clicked, parentMenu: menu, node: node }); + + function inner_clicked(v) + { + if(!node) + return; + node.shape = v; + node.setDirtyCanvas(true); + } + + return false; +} + +LGraphCanvas.onMenuNodeRemove = function( value, options, e, menu, node ) +{ + if(!node) + throw("no node passed"); + + if(node.removable === false) + return; + + node.graph.remove(node); + node.setDirtyCanvas(true,true); +} + +LGraphCanvas.onMenuNodeClone = function( value, options, e, menu, node ) +{ + if(node.clonable == false) return; + var newnode = node.clone(); + if(!newnode) + return; + newnode.pos = [node.pos[0]+5,node.pos[1]+5]; + node.graph.add(newnode); + node.setDirtyCanvas(true,true); +} + +LGraphCanvas.node_colors = { + "red": { color:"#322", bgcolor:"#533", groupcolor: "#A88" }, + "brown": { color:"#332922", bgcolor:"#593930", groupcolor: "#b06634" }, + "green": { color:"#232", bgcolor:"#353", groupcolor: "#8A8" }, + "blue": { color:"#223", bgcolor:"#335", groupcolor: "#88A" }, + "pale_blue": { color:"#2a363b", bgcolor:"#3f5159", groupcolor: "#3f789e" }, + "cyan": { color:"#233", bgcolor:"#355", groupcolor: "#8AA" }, + "purple": { color:"#323", bgcolor:"#535", groupcolor: "#a1309b" }, + "yellow": { color:"#432", bgcolor:"#653", groupcolor: "#b58b2a" }, + "black": { color:"#222", bgcolor:"#000", groupcolor: "#444" } +}; + +LGraphCanvas.prototype.getCanvasMenuOptions = function() +{ + var options = null; + if(this.getMenuOptions) + options = this.getMenuOptions(); + else + { + options = [ + { content:"Add Node", has_submenu: true, callback: LGraphCanvas.onMenuAdd }, + { content:"Add Group", callback: LGraphCanvas.onGroupAdd } + //{content:"Collapse All", callback: LGraphCanvas.onMenuCollapseAll } + ]; + + if(this._graph_stack && this._graph_stack.length > 0) + options = [{content:"Close subgraph", callback: this.closeSubgraph.bind(this) },null].concat(options); + } + + if(this.getExtraMenuOptions) + { + var extra = this.getExtraMenuOptions(this,options); + if(extra) + options = options.concat( extra ); + } + + return options; +} + +//called by processContextMenu to extract the menu list +LGraphCanvas.prototype.getNodeMenuOptions = function( node ) +{ + var options = null; + + if(node.getMenuOptions) + options = node.getMenuOptions(this); + else + options = [ + {content:"Inputs", has_submenu: true, disabled:true, callback: LGraphCanvas.showMenuNodeOptionalInputs }, + {content:"Outputs", has_submenu: true, disabled:true, callback: LGraphCanvas.showMenuNodeOptionalOutputs }, + null, + {content:"Properties", has_submenu: true, callback: LGraphCanvas.onShowMenuNodeProperties }, + null, + {content:"Title", callback: LGraphCanvas.onShowTitleEditor }, + {content:"Mode", has_submenu: true, callback: LGraphCanvas.onMenuNodeMode }, + {content:"Resize", callback: LGraphCanvas.onResizeNode }, + {content:"Collapse", callback: LGraphCanvas.onMenuNodeCollapse }, + {content:"Pin", callback: LGraphCanvas.onMenuNodePin }, + {content:"Colors", has_submenu: true, callback: LGraphCanvas.onMenuNodeColors }, + {content:"Shapes", has_submenu: true, callback: LGraphCanvas.onMenuNodeShapes }, + null + ]; + + if(node.getExtraMenuOptions) + { + var extra = node.getExtraMenuOptions(this); + if(extra) + { + extra.push(null); + options = extra.concat( options ); + } + } + + if( node.clonable !== false ) + options.push({content:"Clone", callback: LGraphCanvas.onMenuNodeClone }); + if( node.removable !== false ) + options.push(null,{content:"Remove", callback: LGraphCanvas.onMenuNodeRemove }); + + if(node.onGetInputs) + { + var inputs = node.onGetInputs(); + if(inputs && inputs.length) + options[0].disabled = false; + } + + if(node.onGetOutputs) + { + var outputs = node.onGetOutputs(); + if(outputs && outputs.length ) + options[1].disabled = false; + } + + if(node.graph && node.graph.onGetNodeMenuOptions ) + node.graph.onGetNodeMenuOptions( options, node ); + + return options; +} + +LGraphCanvas.prototype.getGroupMenuOptions = function( node ) +{ + var o = [ + {content:"Title", callback: LGraphCanvas.onShowTitleEditor }, + {content:"Color", has_submenu: true, callback: LGraphCanvas.onMenuNodeColors }, + null, + {content:"Remove", callback: LGraphCanvas.onMenuNodeRemove } + ]; + + return o; +} + +LGraphCanvas.prototype.processContextMenu = function( node, event ) +{ + var that = this; + var canvas = LGraphCanvas.active_canvas; + var ref_window = canvas.getCanvasWindow(); + + var menu_info = null; + var options = { event: event, callback: inner_option_clicked, node: node }; + + //check if mouse is in input + var slot = null; + if(node) + { + slot = node.getSlotInPosition( event.canvasX, event.canvasY ); + LGraphCanvas.active_node = node; + } + + if(slot) //on slot + { + menu_info = []; + menu_info.push( slot.locked ? "Cannot remove" : { content: "Remove Slot", slot: slot } ); + menu_info.push( { content: "Rename Slot", slot: slot } ); + options.title = (slot.input ? slot.input.type : slot.output.type) || "*"; + if(slot.input && slot.input.type == LiteGraph.ACTION) + options.title = "Action"; + if(slot.output && slot.output.type == LiteGraph.EVENT) + options.title = "Event"; + } + else + { + if( node ) //on node + menu_info = this.getNodeMenuOptions(node); + else + { + var group = this.graph.getGroupOnPos( event.canvasX, event.canvasY ); + if( group ) //on group + { + options.node = group; + menu_info = this.getGroupMenuOptions( group ); + } + else + menu_info = this.getCanvasMenuOptions(); + } + } + + //show menu + if(!menu_info) + return; + + var menu = new LiteGraph.ContextMenu( menu_info, options, ref_window ); + + function inner_option_clicked( v, options, e ) + { + if(!v) + return; + + if(v.content == "Remove Slot") + { + var info = v.slot; + if(info.input) + node.removeInput( info.slot ); + else if(info.output) + node.removeOutput( info.slot ); + return; + } + else if( v.content == "Rename Slot") + { + var info = v.slot; + var dialog = that.createDialog( "Name" , options ); + var input = dialog.querySelector("input"); + dialog.querySelector("button").addEventListener("click",function(e){ + if(input.value) + { + var slot_info = info.input ? node.getInputInfo( info.slot ) : node.getOutputInfo( info.slot ); + if( slot_info ) + slot_info.label = input.value; + that.setDirty(true); + } + dialog.close(); + }); + } + + //if(v.callback) + // return v.callback.call(that, node, options, e, menu, that, event ); + } +} + + + + + + +//API ************************************************* +//like rect but rounded corners +if(this.CanvasRenderingContext2D) +CanvasRenderingContext2D.prototype.roundRect = function (x, y, width, height, radius, radius_low) { + if ( radius === undefined ) { + radius = 5; + } + + if(radius_low === undefined) + radius_low = radius; + + this.moveTo(x + radius, y); + this.lineTo(x + width - radius, y); + this.quadraticCurveTo(x + width, y, x + width, y + radius); + + this.lineTo(x + width, y + height - radius_low); + this.quadraticCurveTo(x + width, y + height, x + width - radius_low, y + height); + this.lineTo(x + radius_low, y + height); + this.quadraticCurveTo(x, y + height, x, y + height - radius_low); + this.lineTo(x, y + radius); + this.quadraticCurveTo(x, y, x + radius, y); +} + +function compareObjects(a,b) +{ + for(var i in a) + if(a[i] != b[i]) + return false; + return true; +} +LiteGraph.compareObjects = compareObjects; + +function distance(a,b) +{ + return Math.sqrt( (b[0] - a[0]) * (b[0] - a[0]) + (b[1] - a[1]) * (b[1] - a[1]) ); +} +LiteGraph.distance = distance; + +function colorToString(c) +{ + return "rgba(" + Math.round(c[0] * 255).toFixed() + "," + Math.round(c[1] * 255).toFixed() + "," + Math.round(c[2] * 255).toFixed() + "," + (c.length == 4 ? c[3].toFixed(2) : "1.0") + ")"; +} +LiteGraph.colorToString = colorToString; + +function isInsideRectangle( x,y, left, top, width, height) +{ + if (left < x && (left + width) > x && + top < y && (top + height) > y) + return true; + return false; +} +LiteGraph.isInsideRectangle = isInsideRectangle; + +//[minx,miny,maxx,maxy] +function growBounding( bounding, x,y) +{ + if(x < bounding[0]) + bounding[0] = x; + else if(x > bounding[2]) + bounding[2] = x; + + if(y < bounding[1]) + bounding[1] = y; + else if(y > bounding[3]) + bounding[3] = y; +} +LiteGraph.growBounding = growBounding; + +//point inside boundin box +function isInsideBounding(p,bb) +{ + if (p[0] < bb[0][0] || + p[1] < bb[0][1] || + p[0] > bb[1][0] || + p[1] > bb[1][1]) + return false; + return true; +} +LiteGraph.isInsideBounding = isInsideBounding; + +//boundings overlap, format: [ startx, starty, width, height ] +function overlapBounding(a,b) +{ + var A_end_x = a[0] + a[2]; + var A_end_y = a[1] + a[3]; + var B_end_x = b[0] + b[2]; + var B_end_y = b[1] + b[3]; + + if ( a[0] > B_end_x || + a[1] > B_end_y || + A_end_x < b[0] || + A_end_y < b[1]) + return false; + return true; +} +LiteGraph.overlapBounding = overlapBounding; + +//Convert a hex value to its decimal value - the inputted hex must be in the +// format of a hex triplet - the kind we use for HTML colours. The function +// will return an array with three values. +function hex2num(hex) { + if(hex.charAt(0) == "#") hex = hex.slice(1); //Remove the '#' char - if there is one. + hex = hex.toUpperCase(); + var hex_alphabets = "0123456789ABCDEF"; + var value = new Array(3); + var k = 0; + var int1,int2; + for(var i=0;i<6;i+=2) { + int1 = hex_alphabets.indexOf(hex.charAt(i)); + int2 = hex_alphabets.indexOf(hex.charAt(i+1)); + value[k] = (int1 * 16) + int2; + k++; + } + return(value); +} + +LiteGraph.hex2num = hex2num; + +//Give a array with three values as the argument and the function will return +// the corresponding hex triplet. +function num2hex(triplet) { + var hex_alphabets = "0123456789ABCDEF"; + var hex = "#"; + var int1,int2; + for(var i=0;i<3;i++) { + int1 = triplet[i] / 16; + int2 = triplet[i] % 16; + + hex += hex_alphabets.charAt(int1) + hex_alphabets.charAt(int2); + } + return(hex); +} + +LiteGraph.num2hex = num2hex; + +/* LiteGraph GUI elements used for canvas editing *************************************/ + +/** +* ContextMenu from LiteGUI +* +* @class ContextMenu +* @constructor +* @param {Array} values (allows object { title: "Nice text", callback: function ... }) +* @param {Object} options [optional] Some options:\ +* - title: title to show on top of the menu +* - callback: function to call when an option is clicked, it receives the item information +* - ignore_item_callbacks: ignores the callback inside the item, it just calls the options.callback +* - event: you can pass a MouseEvent, this way the ContextMenu appears in that position +*/ +function ContextMenu( values, options ) +{ + options = options || {}; + this.options = options; + var that = this; + + //to link a menu with its parent + if(options.parentMenu) + { + if( options.parentMenu.constructor !== this.constructor ) + { + console.error("parentMenu must be of class ContextMenu, ignoring it"); + options.parentMenu = null; + } + else + { + this.parentMenu = options.parentMenu; + this.parentMenu.lock = true; + this.parentMenu.current_submenu = this; + } + } + + if(options.event && options.event.constructor !== MouseEvent && options.event.constructor !== CustomEvent) + { + console.error("Event passed to ContextMenu is not of type MouseEvent or CustomEvent. Ignoring it."); + options.event = null; + } + + var root = document.createElement("div"); + root.className = "litegraph litecontextmenu litemenubar-panel"; + root.style.minWidth = 100; + root.style.minHeight = 100; + root.style.pointerEvents = "none"; + setTimeout( function() { root.style.pointerEvents = "auto"; },100); //delay so the mouse up event is not caugh by this element + + //this prevents the default context browser menu to open in case this menu was created when pressing right button + root.addEventListener("mouseup", function(e){ + e.preventDefault(); return true; + }, true); + root.addEventListener("contextmenu", function(e) { + if(e.button != 2) //right button + return false; + e.preventDefault(); + return false; + },true); + + root.addEventListener("mousedown", function(e){ + if(e.button == 2) + { + that.close(); + e.preventDefault(); return true; + } + }, 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; + + //title + if(options.title) + { + var element = document.createElement("div"); + element.className = "litemenu-title"; + element.innerHTML = options.title; + root.appendChild(element); + } + + //entries + var num = 0; + for(var i in values) + { + var name = values.constructor == Array ? values[i] : i; + if( name != null && name.constructor !== String ) + name = name.content === undefined ? String(name) : name.content; + var value = values[i]; + this.addItem( name, value, options ); + num++; + } + + //close on leave + root.addEventListener("mouseleave", function(e) { + if(that.lock) + return; + that.close(e); + }); + + //insert before checking position + var root_document = document; + if(options.event) + root_document = options.event.target.ownerDocument; + + if(!root_document) + root_document = document; + root_document.body.appendChild(root); + + //compute best position + var left = options.left || 0; + var top = options.top || 0; + if(options.event) + { + left = (options.event.pageX - 10); + top = (options.event.pageY - 10); + if(options.title) + top -= 20; + + if(options.parentMenu) + { + var rect = options.parentMenu.root.getBoundingClientRect(); + left = rect.left + rect.width; + } + + var body_rect = document.body.getBoundingClientRect(); + var root_rect = root.getBoundingClientRect(); + + if(left > (body_rect.width - root_rect.width - 10)) + left = (body_rect.width - root_rect.width - 10); + if(top > (body_rect.height - root_rect.height - 10)) + top = (body_rect.height - root_rect.height - 10); + } + + root.style.left = left + "px"; + root.style.top = top + "px"; +} + +ContextMenu.prototype.addItem = function( name, value, options ) +{ + var that = this; + options = options || {}; + + var element = document.createElement("div"); + element.className = "litemenu-entry submenu"; + + var disabled = false; + + if(value === null) + { + element.classList.add("separator"); + //element.innerHTML = "
" + //continue; + } + else + { + element.innerHTML = value && value.title ? value.title : name; + element.value = value; + + if(value) + { + if(value.disabled) + { + disabled = true; + element.classList.add("disabled"); + } + if(value.submenu || value.has_submenu) + element.classList.add("has_submenu"); + } + + if(typeof(value) == "function") + { + element.dataset["value"] = name; + element.onclick_callback = value; + } + else + element.dataset["value"] = value; + + if(value.className) + element.className += " " + value.className; + } + + this.root.appendChild(element); + if(!disabled) + element.addEventListener("click", inner_onclick); + if(options.autoopen) + element.addEventListener("mouseenter", inner_over); + + function inner_over(e) + { + var value = this.value; + if(!value || !value.has_submenu) + return; + inner_onclick.call(this,e); + } + + //menu option clicked + function inner_onclick(e) { + var value = this.value; + var close_parent = true; + + if(that.current_submenu) + that.current_submenu.close(e); + + //global callback + if(options.callback) + { + var r = options.callback.call( this, value, options, e, that, options.node ); + if(r === true) + close_parent = false; + } + + //special cases + if(value) + { + if (value.callback && !options.ignore_item_callbacks && value.disabled !== true ) //item callback + { + var r = value.callback.call( this, value, options, e, that, options.node ); + if(r === true) + close_parent = false; + } + if(value.submenu) + { + if(!value.submenu.options) + throw("ContextMenu submenu needs options"); + var submenu = new that.constructor( value.submenu.options, { + callback: value.submenu.callback, + event: e, + parentMenu: that, + ignore_item_callbacks: value.submenu.ignore_item_callbacks, + title: value.submenu.title, + autoopen: options.autoopen + }); + close_parent = false; + } + } + + if(close_parent && !that.lock) + that.close(); + } + + return element; +} + +ContextMenu.prototype.close = function(e, ignore_parent_menu) +{ + if(this.root.parentNode) + this.root.parentNode.removeChild( this.root ); + if(this.parentMenu && !ignore_parent_menu) + { + this.parentMenu.lock = false; + this.parentMenu.current_submenu = null; + if( e === undefined ) + this.parentMenu.close(); + else if( e && !ContextMenu.isCursorOverElement( e, this.parentMenu.root) ) + { + ContextMenu.trigger( this.parentMenu.root, "mouseleave", e ); + } + } + if(this.current_submenu) + this.current_submenu.close(e, true); +} + +//this code is used to trigger events easily (used in the context menu mouseleave +ContextMenu.trigger = function(element, event_name, params, origin) +{ + var evt = document.createEvent( 'CustomEvent' ); + evt.initCustomEvent( event_name, true,true, params ); //canBubble, cancelable, detail + evt.srcElement = origin; + if( element.dispatchEvent ) + element.dispatchEvent( evt ); + else if( element.__events ) + element.__events.dispatchEvent( evt ); + //else nothing seems binded here so nothing to do + return evt; +} + +//returns the top most menu +ContextMenu.prototype.getTopMenu = function() +{ + if( this.options.parentMenu ) + return this.options.parentMenu.getTopMenu(); + return this; +} + +ContextMenu.prototype.getFirstEvent = function() +{ + if( this.options.parentMenu ) + return this.options.parentMenu.getFirstEvent(); + return this.options.event; +} + + + +ContextMenu.isCursorOverElement = function( event, element ) +{ + var left = event.pageX; + var top = event.pageY; + var rect = element.getBoundingClientRect(); + if(!rect) + return false; + if(top > rect.top && top < (rect.top + rect.height) && + left > rect.left && left < (rect.left + rect.width) ) + return true; + return false; +} + + + +LiteGraph.ContextMenu = ContextMenu; + +LiteGraph.closeAllContextMenus = function( ref_window ) +{ + ref_window = ref_window || window; + + var elements = ref_window.document.querySelectorAll(".litecontextmenu"); + if(!elements.length) + return; + + var result = []; + for(var i = 0; i < elements.length; i++) + result.push(elements[i]); + + for(var i in result) + { + if(result[i].close) + result[i].close(); + else if(result[i].parentNode) + result[i].parentNode.removeChild( result[i] ); + } +} + +LiteGraph.extendClass = function ( target, origin ) +{ + for(var i in origin) //copy class properties + { + if(target.hasOwnProperty(i)) + continue; + target[i] = origin[i]; + } + + if(origin.prototype) //copy prototype properties + for(var i in origin.prototype) //only enumerables + { + if(!origin.prototype.hasOwnProperty(i)) + continue; + + if(target.prototype.hasOwnProperty(i)) //avoid overwritting existing ones + continue; + + //copy getters + if(origin.prototype.__lookupGetter__(i)) + target.prototype.__defineGetter__(i, origin.prototype.__lookupGetter__(i)); + else + target.prototype[i] = origin.prototype[i]; + + //and setters + if(origin.prototype.__lookupSetter__(i)) + target.prototype.__defineSetter__(i, origin.prototype.__lookupSetter__(i)); + } +} + +//used to create nodes from wrapping functions +LiteGraph.getParameterNames = function(func) { + return (func + '') + .replace(/[/][/].*$/mg,'') // strip single-line comments + .replace(/\s+/g, '') // strip white space + .replace(/[/][*][^/*]*[*][/]/g, '') // strip multi-line comments /**/ + .split('){', 1)[0].replace(/^[^(]*[(]/, '') // extract the parameters + .replace(/=[^,]+/g, '') // strip any ES6 defaults + .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 || + window.mozRequestAnimationFrame || + (function( callback ){ + window.setTimeout(callback, 1000 / 60); + }); +} + +})(this); + +if(typeof(exports) != "undefined") + exports.LiteGraph = this.LiteGraph; + +//basic nodes +(function(global){ +var LiteGraph = global.LiteGraph; + +//Constant +function Time() +{ + this.addOutput("in ms","number"); + this.addOutput("in sec","number"); +} + +Time.title = "Time"; +Time.desc = "Time"; + +Time.prototype.onExecute = function() +{ + this.setOutputData(0, this.graph.globaltime * 1000 ); + this.setOutputData(1, this.graph.globaltime ); +} + +LiteGraph.registerNodeType("basic/time", Time); + + +//Subgraph: a node that contains a graph +function Subgraph() +{ + var that = this; + this.size = [120,80]; + + //create inner graph + this.subgraph = new LGraph(); + this.subgraph._subgraph_node = this; + this.subgraph._is_subgraph = true; + + this.subgraph.onGlobalInputAdded = this.onSubgraphNewGlobalInput.bind(this); + this.subgraph.onGlobalInputRenamed = this.onSubgraphRenamedGlobalInput.bind(this); + this.subgraph.onGlobalInputTypeChanged = this.onSubgraphTypeChangeGlobalInput.bind(this); + + this.subgraph.onGlobalOutputAdded = this.onSubgraphNewGlobalOutput.bind(this); + this.subgraph.onGlobalOutputRenamed = this.onSubgraphRenamedGlobalOutput.bind(this); + this.subgraph.onGlobalOutputTypeChanged = this.onSubgraphTypeChangeGlobalOutput.bind(this); + + this.addWidget("button","Open Graph",null,function( widget, graphcanvas ){ graphcanvas.openSubgraph(that.subgraph) }); + + this.bgcolor = "#353"; +} + +Subgraph.title = "Subgraph"; +Subgraph.desc = "Graph inside a node"; + +Subgraph.prototype.onSubgraphNewGlobalInput = function(name, type) +{ + //add input to the node + this.addInput(name, type); +} + +Subgraph.prototype.onSubgraphRenamedGlobalInput = function(oldname, name) +{ + var slot = this.findInputSlot( oldname ); + if(slot == -1) + return; + var info = this.getInputInfo(slot); + info.name = name; +} + +Subgraph.prototype.onSubgraphTypeChangeGlobalInput = function(name, type) +{ + var slot = this.findInputSlot( name ); + if(slot == -1) + return; + var info = this.getInputInfo(slot); + info.type = type; +} + + +Subgraph.prototype.onSubgraphNewGlobalOutput = function(name, type) +{ + //add output to the node + this.addOutput(name, type); +} + + +Subgraph.prototype.onSubgraphRenamedGlobalOutput = function(oldname, name) +{ + var slot = this.findOutputSlot( oldname ); + if(slot == -1) + return; + var info = this.getOutputInfo(slot); + info.name = name; +} + +Subgraph.prototype.onSubgraphTypeChangeGlobalOutput = function(name, type) +{ + var slot = this.findOutputSlot( name ); + if(slot == -1) + return; + var info = this.getOutputInfo(slot); + info.type = type; +} + + +Subgraph.prototype.getExtraMenuOptions = function(graphcanvas) +{ + var that = this; + return [ {content:"Open", callback: + function() { + graphcanvas.openSubgraph( that.subgraph ); + } + }]; +} + +Subgraph.prototype.onDrawForeground = function( ctx, graphcanvas ) +{ + /* + var node = this; + ctx.globalAlpha = 0.75; + graphcanvas.guiButton( ctx, [0,this.size[1] - 20, this.size[0], 19 ], "Open", function(){ graphcanvas.openSubgraph(node.subgraph); }); + ctx.globalAlpha = 1; + */ +} + +Subgraph.prototype.onResize = function(size) +{ + size[1] += 20; +} + +Subgraph.prototype.onExecute = function() +{ + //send inputs to subgraph global inputs + if(this.inputs) + for(var i = 0; i < this.inputs.length; i++) + { + var input = this.inputs[i]; + var value = this.getInputData(i); + this.subgraph.setGlobalInputData( input.name, value ); + } + + //execute + this.subgraph.runStep(); + + //send subgraph global outputs to outputs + if(this.outputs) + for(var i = 0; i < this.outputs.length; i++) + { + var output = this.outputs[i]; + var value = this.subgraph.getGlobalOutputData( output.name ); + this.setOutputData(i, value); + } +} + +Subgraph.prototype.configure = function(o) +{ + LGraphNode.prototype.configure.call(this, o); + //this.subgraph.configure(o.graph); +} + +Subgraph.prototype.serialize = function() +{ + var data = LGraphNode.prototype.serialize.call(this); + data.subgraph = this.subgraph.serialize(); + return data; +} + +Subgraph.prototype.clone = function() +{ + var node = LiteGraph.createNode(this.type); + var data = this.serialize(); + delete data["id"]; + delete data["inputs"]; + delete data["outputs"]; + node.configure(data); + return node; +} + + +LiteGraph.registerNodeType("graph/subgraph", Subgraph ); + + +//Input for a subgraph +function GlobalInput() +{ + + //random name to avoid problems with other outputs when added + var input_name = "input_" + (Math.random()*1000).toFixed(); + + this.addOutput(input_name, null ); + + this.properties = { name: input_name, type: null }; + + var that = this; + + Object.defineProperty( this.properties, "name", { + get: function() { + return input_name; + }, + set: function(v) { + if(v == "") + return; + + var info = that.getOutputInfo(0); + if(info.name == v) + return; + info.name = v; + if(that.graph) + that.graph.renameGlobalInput(input_name, v); + input_name = v; + }, + enumerable: true + }); + + Object.defineProperty( this.properties, "type", { + get: function() { return that.outputs[0].type; }, + set: function(v) { + that.outputs[0].type = v; + if(that.graph) + that.graph.changeGlobalInputType(input_name, that.outputs[0].type); + }, + enumerable: true + }); +} + +GlobalInput.title = "Input"; +GlobalInput.desc = "Input of the graph"; + +//When added to graph tell the graph this is a new global input +GlobalInput.prototype.onAdded = function() +{ + this.graph.addGlobalInput( this.properties.name, this.properties.type ); +} + +GlobalInput.prototype.onExecute = function() +{ + var name = this.properties.name; + + //read from global input + var data = this.graph.global_inputs[name]; + if(!data) return; + + //put through output + this.setOutputData(0,data.value); +} + +LiteGraph.registerNodeType("graph/input", GlobalInput); + + + +//Output for a subgraph +function GlobalOutput() +{ + //random name to avoid problems with other outputs when added + var output_name = "output_" + (Math.random()*1000).toFixed(); + + this.addInput(output_name, null); + + this._value = null; + + this.properties = {name: output_name, type: null }; + + var that = this; + + Object.defineProperty(this.properties, "name", { + get: function() { + return output_name; + }, + set: function(v) { + if(v == "") + return; + + var info = that.getInputInfo(0); + if(info.name == v) + return; + info.name = v; + if(that.graph) + that.graph.renameGlobalOutput(output_name, v); + output_name = v; + }, + enumerable: true + }); + + Object.defineProperty(this.properties, "type", { + get: function() { return that.inputs[0].type; }, + set: function(v) { + that.inputs[0].type = v; + if(that.graph) + that.graph.changeGlobalInputType( output_name, that.inputs[0].type ); + }, + enumerable: true + }); +} + +GlobalOutput.title = "Output"; +GlobalOutput.desc = "Output of the graph"; + +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._value = this.getInputData(0); + this.graph.setGlobalOutputData( this.properties.name, this._value ); +} + +LiteGraph.registerNodeType("graph/output", GlobalOutput); + + + +//Constant +function Constant() +{ + this.addOutput("value","number"); + this.addProperty( "value", 1.0 ); + this.editable = { property:"value", type:"number" }; +} + +Constant.title = "Const"; +Constant.desc = "Constant value"; + + +Constant.prototype.setValue = function(v) +{ + if( typeof(v) == "string") v = parseFloat(v); + this.properties["value"] = v; + this.setDirtyCanvas(true); +}; + +Constant.prototype.onExecute = function() +{ + this.setOutputData(0, parseFloat( this.properties["value"] ) ); +} + +Constant.prototype.onDrawBackground = function(ctx) +{ + //show the current value + this.outputs[0].label = this.properties["value"].toFixed(3); +} + +Constant.prototype.onWidget = function(e,widget) +{ + if(widget.name == "value") + this.setValue(widget.value); +} + +LiteGraph.registerNodeType("basic/const", Constant); + + +//Watch a value in the editor +function Watch() +{ + this.size = [60,20]; + this.addInput("value",0,{label:""}); + this.addOutput("value",0,{label:""}); + this.addProperty( "value", "" ); +} + +Watch.title = "Watch"; +Watch.desc = "Show value of input"; + +Watch.prototype.onExecute = function() +{ + this.properties.value = this.getInputData(0); + this.setOutputData(0, this.properties.value); +} + +Watch.prototype.onDrawBackground = function(ctx) +{ + //show the current value + if(this.inputs[0] && this.properties["value"] != null) + { + if (this.properties["value"].constructor === Number ) + this.inputs[0].label = this.properties["value"].toFixed(3); + else + this.inputs[0].label = String(this.properties.value); + } +} + +LiteGraph.registerNodeType("basic/watch", Watch); + +//Watch a value in the editor +function Pass() +{ + this.addInput("in",0); + this.addOutput("out",0); + this.size = [40,20]; +} + +Pass.title = "Pass"; +Pass.desc = "Allows to connect different types"; + +Pass.prototype.onExecute = function() +{ + this.setOutputData( 0, this.getInputData(0) ); +} + +LiteGraph.registerNodeType("basic/pass", Pass); + + +//Show value inside the debug console +function Console() +{ + this.mode = LiteGraph.ON_EVENT; + this.size = [60,20]; + this.addProperty( "msg", "" ); + this.addInput("log", LiteGraph.EVENT); + this.addInput("msg",0); +} + +Console.title = "Console"; +Console.desc = "Show value inside the console"; + +Console.prototype.onAction = function(action, param) +{ + if(action == "log") + console.log( param ); + else if(action == "warn") + console.warn( param ); + else if(action == "error") + console.error( param ); +} + +Console.prototype.onExecute = function() +{ + var msg = this.getInputData(1); + if(msg !== null) + this.properties.msg = msg; + console.log(msg); +} + +Console.prototype.onGetInputs = function() +{ + return [["log",LiteGraph.ACTION],["warn",LiteGraph.ACTION],["error",LiteGraph.ACTION]]; +} + +LiteGraph.registerNodeType("basic/console", Console ); + + + +//Show value inside the debug console +function NodeScript() +{ + this.size = [60,20]; + this.addProperty( "onExecute", "" ); + this.addInput("in", ""); + this.addInput("in2", ""); + this.addOutput("out", ""); + this.addOutput("out2", ""); + + this._func = null; +} + +NodeScript.title = "Script"; +NodeScript.desc = "executes a code"; + +NodeScript.widgets_info = { + "onExecute": { type:"code" } +}; + +NodeScript.prototype.onPropertyChanged = function(name,value) +{ + if(name == "onExecute" && LiteGraph.allow_scripts ) + { + this._func = null; + try + { + this._func = new Function( value ); + } + catch (err) + { + console.error("Error parsing script"); + console.error(err); + } + } +} + +NodeScript.prototype.onExecute = function() +{ + if(!this._func) + return; + + try + { + this._func.call(this); + } + catch (err) + { + console.error("Error in script"); + console.error(err); + } +} + +LiteGraph.registerNodeType("basic/script", NodeScript ); + + +})(this); +//event related nodes +(function(global){ +var LiteGraph = global.LiteGraph; + +//Show value inside the debug console +function LogEvent() +{ + this.size = [60,20]; + this.addInput("event", LiteGraph.ACTION); +} + +LogEvent.title = "Log Event"; +LogEvent.desc = "Log event in console"; + +LogEvent.prototype.onAction = function( action, param ) +{ + console.log( action, param ); +} + +LiteGraph.registerNodeType("events/log", LogEvent ); + + +//Filter events +function FilterEvent() +{ + this.size = [60,20]; + this.addInput("event", LiteGraph.ACTION); + this.addOutput("event", LiteGraph.EVENT); + this.properties = { + equal_to: "", + has_property:"", + property_equal_to: "" + }; +} + +FilterEvent.title = "Filter Event"; +FilterEvent.desc = "Blocks events that do not match the filter"; + +FilterEvent.prototype.onAction = function( action, param ) +{ + if( param == null ) + return; + + if( this.properties.equal_to && this.properties.equal_to != param ) + return; + + if( this.properties.has_property ) + { + var prop = param[ this.properties.has_property ]; + if( prop == null ) + return; + + if( this.properties.property_equal_to && this.properties.property_equal_to != prop ) + return; + } + + this.triggerSlot(0,param); +} + +LiteGraph.registerNodeType("events/filter", FilterEvent ); + +/* +//Filter events +function SetModeNode() +{ + this.size = [60,20]; + this.addInput("event", LiteGraph.ACTION); + this.addOutput("event", LiteGraph.EVENT); + this.properties = { + equal_to: "", + has_property:"", + property_equal_to: "" + }; +} + +SetModeNode.title = "Set Node Mode"; +SetModeNode.desc = "Changes a node mode"; + +SetModeNode.prototype.onAction = function( action, param ) +{ + if( param == null ) + return; + + if( this.properties.equal_to && this.properties.equal_to != param ) + return; + + if( this.properties.has_property ) + { + var prop = param[ this.properties.has_property ]; + if( prop == null ) + return; + + if( this.properties.property_equal_to && this.properties.property_equal_to != prop ) + return; + } + + this.triggerSlot(0,param); +} + +LiteGraph.registerNodeType("events/set_mode", SetModeNode ); +*/ + +//Show value inside the debug console +function DelayEvent() +{ + this.size = [60,20]; + this.addProperty( "time", 1000 ); + this.addInput("event", LiteGraph.ACTION); + this.addOutput("on_time", LiteGraph.EVENT); + + this._pending = []; +} + +DelayEvent.title = "Delay"; +DelayEvent.desc = "Delays one event"; + +DelayEvent.prototype.onAction = function(action, param) +{ + this._pending.push([ this.properties.time, param ]); +} + +DelayEvent.prototype.onExecute = function() +{ + var dt = this.graph.elapsed_time * 1000; //in ms + + for(var i = 0; i < this._pending.length; ++i) + { + var action = this._pending[i]; + action[0] -= dt; + if( action[0] > 0 ) + continue; + + //remove + this._pending.splice(i,1); + --i; + + //trigger + this.trigger(null, action[1]); + } +} + +DelayEvent.prototype.onGetInputs = function() +{ + return [["event",LiteGraph.ACTION]]; +} + +LiteGraph.registerNodeType("events/delay", DelayEvent ); + + +})(this); +//widgets +(function(global){ +var LiteGraph = global.LiteGraph; + + /* Button ****************/ + + function WidgetButton() + { + this.addOutput( "clicked", LiteGraph.EVENT ); + this.addProperty( "text","" ); + this.addProperty( "font_size", 40 ); + this.addProperty( "message", "" ); + this.size = [64,84]; + } + + WidgetButton.title = "Button"; + WidgetButton.desc = "Triggers an event"; + + WidgetButton.font = "Arial"; + WidgetButton.prototype.onDrawForeground = function(ctx) + { + if(this.flags.collapsed) + return; + + //ctx.font = "40px Arial"; + //ctx.textAlign = "center"; + ctx.fillStyle = "black"; + ctx.fillRect(1,1,this.size[0] - 3, this.size[1] - 3); + ctx.fillStyle = "#AAF"; + ctx.fillRect(0,0,this.size[0] - 3, this.size[1] - 3); + ctx.fillStyle = this.clicked ? "white" : (this.mouseOver ? "#668" : "#334"); + ctx.fillRect(1,1,this.size[0] - 4, this.size[1] - 4); + + if( this.properties.text || this.properties.text === 0 ) + { + var font_size = this.properties.font_size || 30; + ctx.textAlign = "center"; + ctx.fillStyle = this.clicked ? "black" : "white"; + ctx.font = font_size + "px " + WidgetButton.font; + ctx.fillText( this.properties.text, this.size[0] * 0.5, this.size[1] * 0.5 + font_size * 0.3 ); + ctx.textAlign = "left"; + } + } + + WidgetButton.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.clicked = true; + this.trigger( "clicked", this.properties.message ); + return true; + } + } + + WidgetButton.prototype.onMouseUp = function(e) + { + this.clicked = false; + } + + + LiteGraph.registerNodeType("widget/button", WidgetButton ); + + + function WidgetToggle() + { + this.addInput( "", "boolean" ); + this.addInput( "e", LiteGraph.ACTION ); + 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.onAction = function(action) + { + this.properties.value = !this.properties.value; + this.trigger( "e", this.properties.value ); + } + + 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.graph._version++; + this.trigger( "e", this.properties.value ); + return true; + } + } + + LiteGraph.registerNodeType("widget/toggle", WidgetToggle ); + + /* Number ****************/ + + function WidgetNumber() + { + this.addOutput("",'number'); + this.size = [74,54]; + this.properties = {min:-1000,max:1000,value:1,step:1}; + this.old_y = -1; + this._remainder = 0; + this._precision = 0; + this.mouse_captured = false; + } + + WidgetNumber.title = "Number"; + WidgetNumber.desc = "Widget to select number value"; + + WidgetNumber.pixels_threshold = 10; + WidgetNumber.markers_color = "#666"; + + WidgetNumber.prototype.onDrawForeground = function(ctx) + { + var x = this.size[0]*0.5; + var h = this.size[1]; + if(h > 30) + { + ctx.fillStyle = WidgetNumber.markers_color; + ctx.beginPath(); ctx.moveTo(x,h*0.1); ctx.lineTo(x+h*0.1,h*0.2); ctx.lineTo(x+h*-0.1,h*0.2); ctx.fill(); + ctx.beginPath(); ctx.moveTo(x,h*0.9); ctx.lineTo(x+h*0.1,h*0.8); ctx.lineTo(x+h*-0.1,h*0.8); ctx.fill(); + ctx.font = (h * 0.7).toFixed(1) + "px Arial"; + } + else + ctx.font = (h * 0.8).toFixed(1) + "px Arial"; + + ctx.textAlign = "center"; + ctx.font = (h * 0.7).toFixed(1) + "px Arial"; + ctx.fillStyle = "#EEE"; + ctx.fillText( this.properties.value.toFixed( this._precision ), x, h * 0.75 ); + } + + WidgetNumber.prototype.onExecute = function() + { + this.setOutputData(0, this.properties.value ); + } + + WidgetNumber.prototype.onPropertyChanged = function(name,value) + { + var t = (this.properties.step + "").split("."); + this._precision = t.length > 1 ? t[1].length : 0; + } + + WidgetNumber.prototype.onMouseDown = function(e, pos) + { + if(pos[1] < 0) + return; + + this.old_y = e.canvasY; + this.captureInput(true); + this.mouse_captured = true; + + return true; + } + + WidgetNumber.prototype.onMouseMove = function(e) + { + if(!this.mouse_captured) + return; + + var delta = this.old_y - e.canvasY; + if(e.shiftKey) + delta *= 10; + if(e.metaKey || e.altKey) + delta *= 0.1; + this.old_y = e.canvasY; + + var steps = (this._remainder + delta / WidgetNumber.pixels_threshold); + this._remainder = steps % 1; + steps = steps|0; + + var v = Math.clamp( this.properties.value + steps * this.properties.step, this.properties.min, this.properties.max ); + this.properties.value = v; + this.graph._version++; + this.setDirtyCanvas(true); + } + + WidgetNumber.prototype.onMouseUp = function(e,pos) + { + if(e.click_time < 200) + { + var steps = pos[1] > this.size[1] * 0.5 ? -1 : 1; + this.properties.value = Math.clamp( this.properties.value + steps * this.properties.step, this.properties.min, this.properties.max ); + this.graph._version++; + this.setDirtyCanvas(true); + } + + if( this.mouse_captured ) + { + this.mouse_captured = false; + this.captureInput(false); + } + } + + LiteGraph.registerNodeType("widget/number", WidgetNumber ); + + + /* Knob ****************/ + + function WidgetKnob() + { + this.addOutput("",'number'); + this.size = [64,84]; + this.properties = {min:0,max:1,value:0.5,wcolor:"#7AF",size:50}; + } + + WidgetKnob.title = "Knob"; + WidgetKnob.desc = "Circular controller"; + WidgetKnob.widgets = [{name:"increase",text:"+",type:"minibutton"},{name:"decrease",text:"-",type:"minibutton"}]; + + + WidgetKnob.prototype.onAdded = function() + { + this.value = (this.properties["value"] - this.properties["min"]) / (this.properties["max"] - this.properties["min"]); + + this.imgbg = this.loadImage("imgs/knob_bg.png"); + this.imgfg = this.loadImage("imgs/knob_fg.png"); + } + + WidgetKnob.prototype.onDrawImageKnob = function(ctx) + { + if(!this.imgfg || !this.imgfg.width) return; + + var d = this.imgbg.width*0.5; + var scale = this.size[0] / this.imgfg.width; + + ctx.save(); + ctx.translate(0,20); + ctx.scale(scale,scale); + ctx.drawImage(this.imgbg,0,0); + //ctx.drawImage(this.imgfg,0,20); + + ctx.translate(d,d); + ctx.rotate(this.value * (Math.PI*2) * 6/8 + Math.PI * 10/8); + //ctx.rotate(this.value * (Math.PI*2)); + ctx.translate(-d,-d); + ctx.drawImage(this.imgfg,0,0); + + ctx.restore(); + + if(this.title) + { + ctx.font = "bold 16px Criticized,Tahoma"; + ctx.fillStyle="rgba(100,100,100,0.8)"; + ctx.textAlign = "center"; + ctx.fillText(this.title.toUpperCase(), this.size[0] * 0.5, 18 ); + ctx.textAlign = "left"; + } + } + + WidgetKnob.prototype.onDrawVectorKnob = function(ctx) + { + if(!this.imgfg || !this.imgfg.width) return; + + //circle around + ctx.lineWidth = 1; + ctx.strokeStyle= this.mouseOver ? "#FFF" : "#AAA"; + ctx.fillStyle="#000"; + ctx.beginPath(); + ctx.arc(this.size[0] * 0.5,this.size[1] * 0.5 + 10,this.properties.size * 0.5,0,Math.PI*2,true); + ctx.stroke(); + + if(this.value > 0) + { + ctx.strokeStyle=this.properties["wcolor"]; + ctx.lineWidth = (this.properties.size * 0.2); + ctx.beginPath(); + ctx.arc(this.size[0] * 0.5,this.size[1] * 0.5 + 10,this.properties.size * 0.35,Math.PI * -0.5 + Math.PI*2 * this.value,Math.PI * -0.5,true); + ctx.stroke(); + ctx.lineWidth = 1; + } + + ctx.font = (this.properties.size * 0.2) + "px Arial"; + ctx.fillStyle="#AAA"; + ctx.textAlign = "center"; + + var str = this.properties["value"]; + if(typeof(str) == 'number') + str = str.toFixed(2); + + ctx.fillText(str,this.size[0] * 0.5,this.size[1]*0.65); + ctx.textAlign = "left"; + } + + WidgetKnob.prototype.onDrawForeground = function(ctx) + { + this.onDrawImageKnob(ctx); + } + + WidgetKnob.prototype.onExecute = function() + { + this.setOutputData(0, this.properties["value"] ); + + this.boxcolor = LiteGraph.colorToString([this.value,this.value,this.value]); + } + + WidgetKnob.prototype.onMouseDown = function(e) + { + if(!this.imgfg || !this.imgfg.width) return; + + //this.center = [this.imgbg.width * 0.5, this.imgbg.height * 0.5 + 20]; + //this.radius = this.imgbg.width * 0.5; + this.center = [this.size[0] * 0.5, this.size[1] * 0.5 + 20]; + this.radius = this.size[0] * 0.5; + + if(e.canvasY - this.pos[1] < 20 || LiteGraph.distance([e.canvasX,e.canvasY],[this.pos[0] + this.center[0],this.pos[1] + this.center[1]]) > this.radius) + return false; + + this.oldmouse = [ e.canvasX - this.pos[0], e.canvasY - this.pos[1] ]; + this.captureInput(true); + + /* + var tmp = this.localToScreenSpace(0,0); + this.trace(tmp[0] + "," + tmp[1]); */ + return true; + } + + WidgetKnob.prototype.onMouseMove = function(e) + { + if(!this.oldmouse) return; + + var m = [ e.canvasX - this.pos[0], e.canvasY - this.pos[1] ]; + + var v = this.value; + v -= (m[1] - this.oldmouse[1]) * 0.01; + if(v > 1.0) v = 1.0; + else if(v < 0.0) v = 0.0; + + this.value = v; + this.properties["value"] = this.properties["min"] + (this.properties["max"] - this.properties["min"]) * this.value; + + this.oldmouse = m; + this.setDirtyCanvas(true); + } + + WidgetKnob.prototype.onMouseUp = function(e) + { + if(this.oldmouse) + { + this.oldmouse = null; + this.captureInput(false); + } + } + + WidgetKnob.prototype.onMouseLeave = function(e) + { + //this.oldmouse = null; + } + + WidgetKnob.prototype.onWidget = function(e,widget) + { + if(widget.name=="increase") + this.onPropertyChanged("size", this.properties.size + 10); + else if(widget.name=="decrease") + this.onPropertyChanged("size", this.properties.size - 10); + } + + WidgetKnob.prototype.onPropertyChanged = function(name,value) + { + if(name=="wcolor") + this.properties[name] = value; + else if(name=="size") + { + value = parseInt(value); + this.properties[name] = value; + this.size = [value+4,value+24]; + this.setDirtyCanvas(true,true); + } + else if(name=="min" || name=="max" || name=="value") + { + this.properties[name] = parseFloat(value); + } + else + return false; + return true; + } + + LiteGraph.registerNodeType("widget/knob", WidgetKnob); + + //Show value inside the debug console + function WidgetSliderGUI() + { + this.addOutput("","number"); + this.properties = { + value: 0.5, + min: 0, + max: 1, + text: "V" + }; + var that = this; + this.size = [80,60]; + this.slider = this.addWidget("slider","V", this.properties.value, function(v){ that.properties.value = v; }, this.properties ); + } + + WidgetSliderGUI.title = "Internal Slider"; + + WidgetSliderGUI.prototype.onPropertyChanged = function(name,value) + { + if(name == "value") + this.slider.value = value; + } + + WidgetSliderGUI.prototype.onExecute = function() + { + this.setOutputData(0,this.properties.value); + } + + + LiteGraph.registerNodeType("widget/internal_slider", WidgetSliderGUI ); + + //Widget H SLIDER + function WidgetHSlider() + { + this.size = [160,26]; + this.addOutput("",'number'); + this.properties = {wcolor:"#7AF",min:0,max:1,value:0.5}; + } + + WidgetHSlider.title = "H.Slider"; + WidgetHSlider.desc = "Linear slider controller"; + + WidgetHSlider.prototype.onAdded = function() + { + this.value = 0.5; + this.imgfg = this.loadImage("imgs/slider_fg.png"); + } + + WidgetHSlider.prototype.onDrawVectorial = function(ctx) + { + if(!this.imgfg || !this.imgfg.width) return; + + //border + ctx.lineWidth = 1; + ctx.strokeStyle= this.mouseOver ? "#FFF" : "#AAA"; + ctx.fillStyle="#000"; + ctx.beginPath(); + ctx.rect(2,0,this.size[0]-4,20); + ctx.stroke(); + + ctx.fillStyle=this.properties["wcolor"]; + ctx.beginPath(); + ctx.rect(2+(this.size[0]-4-20)*this.value,0, 20,20); + ctx.fill(); + } + + WidgetHSlider.prototype.onDrawImage = function(ctx) + { + if(!this.imgfg || !this.imgfg.width) + return; + + //border + ctx.lineWidth = 1; + ctx.fillStyle="#000"; + ctx.fillRect(2,9,this.size[0]-4,2); + + ctx.strokeStyle= "#333"; + ctx.beginPath(); + ctx.moveTo(2,9); + ctx.lineTo(this.size[0]-4,9); + ctx.stroke(); + + ctx.strokeStyle= "#AAA"; + ctx.beginPath(); + ctx.moveTo(2,11); + ctx.lineTo(this.size[0]-4,11); + ctx.stroke(); + + ctx.drawImage(this.imgfg, 2+(this.size[0]-4)*this.value - this.imgfg.width*0.5,-this.imgfg.height*0.5 + 10); + }, + + WidgetHSlider.prototype.onDrawForeground = function(ctx) + { + this.onDrawImage(ctx); + } + + WidgetHSlider.prototype.onExecute = function() + { + this.properties["value"] = this.properties["min"] + (this.properties["max"] - this.properties["min"]) * this.value; + this.setOutputData(0, this.properties["value"] ); + this.boxcolor = LiteGraph.colorToString([this.value,this.value,this.value]); + } + + WidgetHSlider.prototype.onMouseDown = function(e) + { + if(e.canvasY - this.pos[1] < 0) + return false; + + this.oldmouse = [ e.canvasX - this.pos[0], e.canvasY - this.pos[1] ]; + this.captureInput(true); + return true; + } + + WidgetHSlider.prototype.onMouseMove = function(e) + { + if(!this.oldmouse) return; + + var m = [ e.canvasX - this.pos[0], e.canvasY - this.pos[1] ]; + + var v = this.value; + var delta = (m[0] - this.oldmouse[0]); + v += delta / this.size[0]; + if(v > 1.0) v = 1.0; + else if(v < 0.0) v = 0.0; + + this.value = v; + + this.oldmouse = m; + this.setDirtyCanvas(true); + } + + WidgetHSlider.prototype.onMouseUp = function(e) + { + this.oldmouse = null; + this.captureInput(false); + } + + WidgetHSlider.prototype.onMouseLeave = function(e) + { + //this.oldmouse = null; + } + + WidgetHSlider.prototype.onPropertyChanged = function(name,value) + { + if(name=="wcolor") + this.properties[name] = value; + else + return false; + return true; + } + + LiteGraph.registerNodeType("widget/hslider", WidgetHSlider ); + + + function WidgetProgress() + { + this.size = [160,26]; + this.addInput("",'number'); + this.properties = {min:0,max:1,value:0,wcolor:"#AAF"}; + } + + WidgetProgress.title = "Progress"; + WidgetProgress.desc = "Shows data in linear progress"; + + WidgetProgress.prototype.onExecute = function() + { + var v = this.getInputData(0); + if( v != undefined ) + this.properties["value"] = v; + } + + WidgetProgress.prototype.onDrawForeground = function(ctx) + { + //border + ctx.lineWidth = 1; + ctx.fillStyle=this.properties.wcolor; + var v = (this.properties.value - this.properties.min) / (this.properties.max - this.properties.min); + v = Math.min(1,v); + v = Math.max(0,v); + ctx.fillRect(2,2,(this.size[0]-4)*v,this.size[1]-4); + } + + LiteGraph.registerNodeType("widget/progress", WidgetProgress); + + + /* + LiteGraph.registerNodeType("widget/kpad",{ + title: "KPad", + desc: "bidimensional slider", + size: [200,200], + outputs: [["x",'number'],["y",'number']], + properties:{x:0,y:0,borderColor:"#333",bgcolorTop:"#444",bgcolorBottom:"#000",shadowSize:1, borderRadius:2}, + + createGradient: function(ctx) + { + this.lineargradient = ctx.createLinearGradient(0,0,0,this.size[1]); + this.lineargradient.addColorStop(0,this.properties["bgcolorTop"]); + this.lineargradient.addColorStop(1,this.properties["bgcolorBottom"]); + }, + + onDrawBackground: function(ctx) + { + if(!this.lineargradient) + this.createGradient(ctx); + + ctx.lineWidth = 1; + ctx.strokeStyle = this.properties["borderColor"]; + //ctx.fillStyle = "#ebebeb"; + ctx.fillStyle = this.lineargradient; + + ctx.shadowColor = "#000"; + ctx.shadowOffsetX = 0; + ctx.shadowOffsetY = 0; + ctx.shadowBlur = this.properties["shadowSize"]; + ctx.roundRect(0,0,this.size[0],this.size[1],this.properties["shadowSize"]); + ctx.fill(); + ctx.shadowColor = "rgba(0,0,0,0)"; + ctx.stroke(); + + ctx.fillStyle = "#A00"; + ctx.fillRect(this.size[0] * this.properties["x"] - 5, this.size[1] * this.properties["y"] - 5,10,10); + }, + + onWidget: function(e,widget) + { + if(widget.name == "update") + { + this.lineargradient = null; + this.setDirtyCanvas(true); + } + }, + + onExecute: function() + { + this.setOutputData(0, this.properties["x"] ); + this.setOutputData(1, this.properties["y"] ); + }, + + onMouseDown: function(e) + { + if(e.canvasY - this.pos[1] < 0) + return false; + + this.oldmouse = [ e.canvasX - this.pos[0], e.canvasY - this.pos[1] ]; + this.captureInput(true); + return true; + }, + + onMouseMove: function(e) + { + if(!this.oldmouse) return; + + var m = [ e.canvasX - this.pos[0], e.canvasY - this.pos[1] ]; + + this.properties.x = m[0] / this.size[0]; + this.properties.y = m[1] / this.size[1]; + + if(this.properties.x > 1.0) this.properties.x = 1.0; + else if(this.properties.x < 0.0) this.properties.x = 0.0; + + if(this.properties.y > 1.0) this.properties.y = 1.0; + else if(this.properties.y < 0.0) this.properties.y = 0.0; + + this.oldmouse = m; + this.setDirtyCanvas(true); + }, + + onMouseUp: function(e) + { + if(this.oldmouse) + { + this.oldmouse = null; + this.captureInput(false); + } + }, + + onMouseLeave: function(e) + { + //this.oldmouse = null; + } + }); + + + + LiteGraph.registerNodeType("widget/button", { + title: "Button", + desc: "A send command button", + + widgets: [{name:"test",text:"Test Button",type:"button"}], + size: [100,40], + properties:{text:"clickme",command:"",color:"#7AF",bgcolorTop:"#f0f0f0",bgcolorBottom:"#e0e0e0",fontsize:"16"}, + outputs:[["M","module"]], + + createGradient: function(ctx) + { + this.lineargradient = ctx.createLinearGradient(0,0,0,this.size[1]); + this.lineargradient.addColorStop(0,this.properties["bgcolorTop"]); + this.lineargradient.addColorStop(1,this.properties["bgcolorBottom"]); + }, + + drawVectorShape: function(ctx) + { + ctx.fillStyle = this.mouseOver ? this.properties["color"] : "#AAA"; + + if(this.clicking) + ctx.fillStyle = "#FFF"; + + ctx.strokeStyle = "#AAA"; + ctx.roundRect(5,5,this.size[0] - 10,this.size[1] - 10,4); + ctx.stroke(); + + if(this.mouseOver) + ctx.fill(); + + //ctx.fillRect(5,20,this.size[0] - 10,this.size[1] - 30); + + ctx.fillStyle = this.mouseOver ? "#000" : "#AAA"; + ctx.font = "bold " + this.properties["fontsize"] + "px Criticized,Tahoma"; + ctx.textAlign = "center"; + ctx.fillText(this.properties["text"],this.size[0]*0.5,this.size[1]*0.5 + 0.5*parseInt(this.properties["fontsize"])); + ctx.textAlign = "left"; + }, + + drawBevelShape: function(ctx) + { + ctx.shadowColor = "#000"; + ctx.shadowOffsetX = 0; + ctx.shadowOffsetY = 0; + ctx.shadowBlur = this.properties["shadowSize"]; + + if(!this.lineargradient) + this.createGradient(ctx); + + ctx.fillStyle = this.mouseOver ? this.properties["color"] : this.lineargradient; + if(this.clicking) + ctx.fillStyle = "#444"; + + ctx.strokeStyle = "#FFF"; + ctx.roundRect(5,5,this.size[0] - 10,this.size[1] - 10,4); + ctx.fill(); + ctx.shadowColor = "rgba(0,0,0,0)"; + ctx.stroke(); + + ctx.fillStyle = this.mouseOver ? "#000" : "#444"; + ctx.font = "bold " + this.properties["fontsize"] + "px Century Gothic"; + ctx.textAlign = "center"; + ctx.fillText(this.properties["text"],this.size[0]*0.5,this.size[1]*0.5 + 0.40*parseInt(this.properties["fontsize"])); + ctx.textAlign = "left"; + }, + + onDrawForeground: function(ctx) + { + this.drawBevelShape(ctx); + }, + + clickButton: function() + { + var module = this.getOutputModule(0); + if(this.properties["command"] && this.properties["command"] != "") + { + if (! module.executeAction(this.properties["command"]) ) + this.trace("Error executing action in other module"); + } + else if(module && module.onTrigger) + { + module.onTrigger(); + } + }, + + onMouseDown: function(e) + { + if(e.canvasY - this.pos[1] < 2) + return false; + this.clickButton(); + this.clicking = true; + return true; + }, + + onMouseUp: function(e) + { + this.clicking = false; + }, + + onExecute: function() + { + }, + + onWidget: function(e,widget) + { + if(widget.name == "test") + { + this.clickButton(); + } + }, + + onPropertyChanged: function(name,value) + { + this.properties[name] = value; + return true; + } + }); + */ + + + function WidgetText() + { + this.addInputs("",0); + this.properties = { value:"...",font:"Arial", fontsize:18, color:"#AAA", align:"left", glowSize:0, decimals:1 }; + } + + WidgetText.title = "Text"; + WidgetText.desc = "Shows the input value"; + WidgetText.widgets = [{name:"resize",text:"Resize box",type:"button"},{name:"led_text",text:"LED",type:"minibutton"},{name:"normal_text",text:"Normal",type:"minibutton"}]; + + WidgetText.prototype.onDrawForeground = function(ctx) + { + //ctx.fillStyle="#000"; + //ctx.fillRect(0,0,100,60); + ctx.fillStyle = this.properties["color"]; + var v = this.properties["value"]; + + if(this.properties["glowSize"]) + { + ctx.shadowColor = this.properties["color"]; + ctx.shadowOffsetX = 0; + ctx.shadowOffsetY = 0; + ctx.shadowBlur = this.properties["glowSize"]; + } + else + ctx.shadowColor = "transparent"; + + var fontsize = this.properties["fontsize"]; + + ctx.textAlign = this.properties["align"]; + ctx.font = fontsize.toString() + "px " + this.properties["font"]; + this.str = typeof(v) == 'number' ? v.toFixed(this.properties["decimals"]) : v; + + if( typeof(this.str) == 'string') + { + var lines = this.str.split("\\n"); + for(var i in lines) + ctx.fillText(lines[i],this.properties["align"] == "left" ? 15 : this.size[0] - 15, fontsize * -0.15 + fontsize * (parseInt(i)+1) ); + } + + ctx.shadowColor = "transparent"; + this.last_ctx = ctx; + ctx.textAlign = "left"; + } + + WidgetText.prototype.onExecute = function() + { + var v = this.getInputData(0); + if(v != null) + this.properties["value"] = v; + //this.setDirtyCanvas(true); + } + + WidgetText.prototype.resize = function() + { + if(!this.last_ctx) return; + + var lines = this.str.split("\\n"); + this.last_ctx.font = this.properties["fontsize"] + "px " + this.properties["font"]; + var max = 0; + for(var i in lines) + { + var w = this.last_ctx.measureText(lines[i]).width; + if(max < w) max = w; + } + this.size[0] = max + 20; + this.size[1] = 4 + lines.length * this.properties["fontsize"]; + + this.setDirtyCanvas(true); + } + + WidgetText.prototype.onWidget = function(e,widget) + { + if(widget.name == "resize") + this.resize(); + else if (widget.name == "led_text") + { + this.properties["font"] = "Digital"; + this.properties["glowSize"] = 4; + this.setDirtyCanvas(true); + } + else if (widget.name == "normal_text") + { + this.properties["font"] = "Arial"; + this.setDirtyCanvas(true); + } + } + + WidgetText.prototype.onPropertyChanged = function(name,value) + { + this.properties[name] = value; + this.str = typeof(value) == 'number' ? value.toFixed(3) : value; + //this.resize(); + return true; + } + + LiteGraph.registerNodeType("widget/text", WidgetText ); + + + function WidgetPanel() + { + this.size = [200,100]; + this.properties = {borderColor:"#ffffff",bgcolorTop:"#f0f0f0",bgcolorBottom:"#e0e0e0",shadowSize:2, borderRadius:3}; + } + + WidgetPanel.title = "Panel"; + WidgetPanel.desc = "Non interactive panel"; + WidgetPanel.widgets = [{name:"update",text:"Update",type:"button"}]; + + + WidgetPanel.prototype.createGradient = function(ctx) + { + if(this.properties["bgcolorTop"] == "" || this.properties["bgcolorBottom"] == "") + { + this.lineargradient = 0; + return; + } + + this.lineargradient = ctx.createLinearGradient(0,0,0,this.size[1]); + this.lineargradient.addColorStop(0,this.properties["bgcolorTop"]); + this.lineargradient.addColorStop(1,this.properties["bgcolorBottom"]); + } + + WidgetPanel.prototype.onDrawForeground = function(ctx) + { + if(this.lineargradient == null) + this.createGradient(ctx); + + if(!this.lineargradient) + return; + + ctx.lineWidth = 1; + ctx.strokeStyle = this.properties["borderColor"]; + //ctx.fillStyle = "#ebebeb"; + ctx.fillStyle = this.lineargradient; + + if(this.properties["shadowSize"]) + { + ctx.shadowColor = "#000"; + ctx.shadowOffsetX = 0; + ctx.shadowOffsetY = 0; + ctx.shadowBlur = this.properties["shadowSize"]; + } + else + ctx.shadowColor = "transparent"; + + ctx.roundRect(0,0,this.size[0]-1,this.size[1]-1,this.properties["shadowSize"]); + ctx.fill(); + ctx.shadowColor = "transparent"; + ctx.stroke(); + } + + WidgetPanel.prototype.onWidget = function(e,widget) + { + if(widget.name == "update") + { + this.lineargradient = null; + this.setDirtyCanvas(true); + } + } + + LiteGraph.registerNodeType("widget/panel", WidgetPanel ); + +})(this); +(function(global){ +var LiteGraph = global.LiteGraph; + +function GamepadInput() +{ + this.addOutput("left_x_axis","number"); + this.addOutput("left_y_axis","number"); + this.addOutput( "button_pressed", LiteGraph.EVENT ); + this.properties = { gamepad_index: 0, threshold: 0.1 }; + + this._left_axis = new Float32Array(2); + this._right_axis = new Float32Array(2); + this._triggers = new Float32Array(2); + this._previous_buttons = new Uint8Array(17); + this._current_buttons = new Uint8Array(17); +} + +GamepadInput.title = "Gamepad"; +GamepadInput.desc = "gets the input of the gamepad"; + +GamepadInput.zero = new Float32Array(2); +GamepadInput.buttons = ["a","b","x","y","lb","rb","lt","rt","back","start","ls","rs","home"]; + +GamepadInput.prototype.onExecute = function() +{ + //get gamepad + var gamepad = this.getGamepad(); + var threshold = this.properties.threshold || 0.0; + + if(gamepad) + { + this._left_axis[0] = Math.abs( gamepad.xbox.axes["lx"] ) > threshold ? gamepad.xbox.axes["lx"] : 0; + this._left_axis[1] = Math.abs( gamepad.xbox.axes["ly"] ) > threshold ? gamepad.xbox.axes["ly"] : 0; + this._right_axis[0] = Math.abs( gamepad.xbox.axes["rx"] ) > threshold ? gamepad.xbox.axes["rx"] : 0; + this._right_axis[1] = Math.abs( gamepad.xbox.axes["ry"] ) > threshold ? gamepad.xbox.axes["ry"] : 0; + this._triggers[0] = Math.abs( gamepad.xbox.axes["ltrigger"] ) > threshold ? gamepad.xbox.axes["ltrigger"] : 0; + this._triggers[1] = Math.abs( gamepad.xbox.axes["rtrigger"] ) > threshold ? gamepad.xbox.axes["rtrigger"] : 0; + } + + if(this.outputs) + { + for(var i = 0; i < this.outputs.length; i++) + { + var output = this.outputs[i]; + if(!output.links || !output.links.length) + continue; + var v = null; + + if(gamepad) + { + switch( output.name ) + { + case "left_axis": v = this._left_axis; break; + case "right_axis": v = this._right_axis; break; + case "left_x_axis": v = this._left_axis[0]; break; + case "left_y_axis": v = this._left_axis[1]; break; + case "right_x_axis": v = this._right_axis[0]; break; + case "right_y_axis": v = this._right_axis[1]; break; + case "trigger_left": v = this._triggers[0]; break; + case "trigger_right": v = this._triggers[1]; break; + case "a_button": v = gamepad.xbox.buttons["a"] ? 1 : 0; break; + case "b_button": v = gamepad.xbox.buttons["b"] ? 1 : 0; break; + case "x_button": v = gamepad.xbox.buttons["x"] ? 1 : 0; break; + case "y_button": v = gamepad.xbox.buttons["y"] ? 1 : 0; break; + case "lb_button": v = gamepad.xbox.buttons["lb"] ? 1 : 0; break; + case "rb_button": v = gamepad.xbox.buttons["rb"] ? 1 : 0; break; + case "ls_button": v = gamepad.xbox.buttons["ls"] ? 1 : 0; break; + case "rs_button": v = gamepad.xbox.buttons["rs"] ? 1 : 0; break; + case "start_button": v = gamepad.xbox.buttons["start"] ? 1 : 0; break; + case "back_button": v = gamepad.xbox.buttons["back"] ? 1 : 0; break; + case "button_pressed": + for(var j = 0; j < this._current_buttons.length; ++j) + { + if( this._current_buttons[j] && !this._previous_buttons[j] ) + this.triggerSlot( i, GamepadInput.buttons[j] ); + } + break; + default: break; + } + } + else + { + //if no gamepad is connected, output 0 + switch( output.name ) + { + case "button_pressed": break; + case "left_axis": + case "right_axis": + v = GamepadInput.zero; + break; + default: + v = 0; + } + } + this.setOutputData(i,v); + } + } +} + +GamepadInput.prototype.getGamepad = function() +{ + var getGamepads = navigator.getGamepads || navigator.webkitGetGamepads || navigator.mozGetGamepads; + if(!getGamepads) + return null; + var gamepads = getGamepads.call(navigator); + var gamepad = null; + + this._previous_buttons.set( this._current_buttons ); + + //pick the first connected + for(var i = this.properties.gamepad_index; i < 4; i++) + { + if (gamepads[i]) + { + gamepad = gamepads[i]; + + //xbox controller mapping + var xbox = this.xbox_mapping; + if(!xbox) + xbox = this.xbox_mapping = { axes:[], buttons:{}, hat: ""}; + + xbox.axes["lx"] = gamepad.axes[0]; + xbox.axes["ly"] = gamepad.axes[1]; + xbox.axes["rx"] = gamepad.axes[2]; + xbox.axes["ry"] = gamepad.axes[3]; + xbox.axes["ltrigger"] = gamepad.buttons[6].value; + xbox.axes["rtrigger"] = gamepad.buttons[7].value; + + for(var j = 0; j < gamepad.buttons.length; j++) + { + this._current_buttons[j] = gamepad.buttons[j].pressed; + + //mapping of XBOX + switch(j) //I use a switch to ensure that a player with another gamepad could play + { + case 0: xbox.buttons["a"] = gamepad.buttons[j].pressed; break; + case 1: xbox.buttons["b"] = gamepad.buttons[j].pressed; break; + case 2: xbox.buttons["x"] = gamepad.buttons[j].pressed; break; + case 3: xbox.buttons["y"] = gamepad.buttons[j].pressed; break; + case 4: xbox.buttons["lb"] = gamepad.buttons[j].pressed; break; + case 5: xbox.buttons["rb"] = gamepad.buttons[j].pressed; break; + case 6: xbox.buttons["lt"] = gamepad.buttons[j].pressed; break; + case 7: xbox.buttons["rt"] = gamepad.buttons[j].pressed; break; + case 8: xbox.buttons["back"] = gamepad.buttons[j].pressed; break; + case 9: xbox.buttons["start"] = gamepad.buttons[j].pressed; break; + case 10: xbox.buttons["ls"] = gamepad.buttons[j].pressed; break; + case 11: xbox.buttons["rs"] = gamepad.buttons[j].pressed; break; + case 12: if( gamepad.buttons[j].pressed) xbox.hat += "up"; break; + case 13: if( gamepad.buttons[j].pressed) xbox.hat += "down"; break; + case 14: if( gamepad.buttons[j].pressed) xbox.hat += "left"; break; + case 15: if( gamepad.buttons[j].pressed) xbox.hat += "right"; break; + case 16: xbox.buttons["home"] = gamepad.buttons[j].pressed; break; + default: + } + } + gamepad.xbox = xbox; + return gamepad; + } + } +} + +GamepadInput.prototype.onDrawBackground = function(ctx) +{ + //render gamepad state? + var la = this._left_axis; + var ra = this._right_axis; + ctx.strokeStyle = "#88A"; + ctx.strokeRect( (la[0] + 1) * 0.5 * this.size[0] - 4, (la[1] + 1) * 0.5 * this.size[1] - 4, 8, 8 ); + ctx.strokeStyle = "#8A8"; + ctx.strokeRect( (ra[0] + 1) * 0.5 * this.size[0] - 4, (ra[1] + 1) * 0.5 * this.size[1] - 4, 8, 8 ); + var h = this.size[1] / this._current_buttons.length + ctx.fillStyle = "#AEB"; + for(var i = 0; i < this._current_buttons.length; ++i) + if(this._current_buttons[i]) + ctx.fillRect( 0, h * i, 6, h); +} + +GamepadInput.prototype.onGetOutputs = function() { + return [ + ["left_axis","vec2"], + ["right_axis","vec2"], + ["left_x_axis","number"], + ["left_y_axis","number"], + ["right_x_axis","number"], + ["right_y_axis","number"], + ["trigger_left","number"], + ["trigger_right","number"], + ["a_button","number"], + ["b_button","number"], + ["x_button","number"], + ["y_button","number"], + ["lb_button","number"], + ["rb_button","number"], + ["ls_button","number"], + ["rs_button","number"], + ["start","number"], + ["back","number"], + ["button_pressed", LiteGraph.EVENT] + ]; +} + +LiteGraph.registerNodeType("input/gamepad", GamepadInput ); + +})(this); +(function(global){ +var LiteGraph = global.LiteGraph; + +//Converter +function Converter() +{ + this.addInput("in","*"); + this.size = [60,20]; +} + +Converter.title = "Converter"; +Converter.desc = "type A to type B"; + +Converter.prototype.onExecute = function() +{ + var v = this.getInputData(0); + if(v == null) + return; + + if(this.outputs) + for(var i = 0; i < this.outputs.length; i++) + { + var output = this.outputs[i]; + if(!output.links || !output.links.length) + continue; + + var result = null; + switch( output.name ) + { + case "number": result = v.length ? v[0] : parseFloat(v); break; + case "vec2": + case "vec3": + case "vec4": + var result = null; + var count = 1; + switch(output.name) + { + case "vec2": count = 2; break; + case "vec3": count = 3; break; + case "vec4": count = 4; break; + } + + var result = new Float32Array( count ); + if( v.length ) + { + for(var j = 0; j < v.length && j < result.length; j++) + result[j] = v[j]; + } + else + result[0] = parseFloat(v); + break; + } + this.setOutputData(i, result); + } +} + +Converter.prototype.onGetOutputs = function() { + return [["number","number"],["vec2","vec2"],["vec3","vec3"],["vec4","vec4"]]; +} + +LiteGraph.registerNodeType("math/converter", Converter ); + + +//Bypass +function Bypass() +{ + this.addInput("in"); + this.addOutput("out"); + this.size = [60,20]; +} + +Bypass.title = "Bypass"; +Bypass.desc = "removes the type"; + +Bypass.prototype.onExecute = function() +{ + var v = this.getInputData(0); + this.setOutputData(0, v); +} + +LiteGraph.registerNodeType("math/bypass", Bypass ); + + + +function MathRange() +{ + this.addInput("in","number",{locked:true}); + this.addOutput("out","number",{locked:true}); + + this.addProperty( "in", 0 ); + this.addProperty( "in_min", 0 ); + this.addProperty( "in_max", 1 ); + this.addProperty( "out_min", 0 ); + this.addProperty( "out_max", 1 ); +} + +MathRange.title = "Range"; +MathRange.desc = "Convert a number from one range to another"; + +MathRange.prototype.onExecute = function() +{ + if(this.inputs) + for(var i = 0; i < this.inputs.length; i++) + { + var input = this.inputs[i]; + var v = this.getInputData(i); + if(v === undefined) + continue; + this.properties[ input.name ] = v; + } + + var v = this.properties["in"]; + if(v === undefined || v === null || v.constructor !== Number) + v = 0; + + var in_min = this.properties.in_min; + var in_max = this.properties.in_max; + var out_min = this.properties.out_min; + var out_max = this.properties.out_max; + + this._last_v = ((v - in_min) / (in_max - in_min)) * (out_max - out_min) + out_min; + this.setOutputData(0, this._last_v ); +} + +MathRange.prototype.onDrawBackground = function(ctx) +{ + //show the current value + if(this._last_v) + this.outputs[0].label = this._last_v.toFixed(3); + else + this.outputs[0].label = "?"; +} + +MathRange.prototype.onGetInputs = function() { + return [["in_min","number"],["in_max","number"],["out_min","number"],["out_max","number"]]; +} + +LiteGraph.registerNodeType("math/range", MathRange); + + + +function MathRand() +{ + this.addOutput("value","number"); + this.addProperty( "min", 0 ); + this.addProperty( "max", 1 ); + this.size = [60,20]; +} + +MathRand.title = "Rand"; +MathRand.desc = "Random number"; + +MathRand.prototype.onExecute = function() +{ + if(this.inputs) + for(var i = 0; i < this.inputs.length; i++) + { + var input = this.inputs[i]; + var v = this.getInputData(i); + if(v === undefined) + continue; + this.properties[input.name] = v; + } + + var min = this.properties.min; + var max = this.properties.max; + this._last_v = Math.random() * (max-min) + min; + this.setOutputData(0, this._last_v ); +} + +MathRand.prototype.onDrawBackground = function(ctx) +{ + //show the current value + if(this._last_v) + this.outputs[0].label = this._last_v.toFixed(3); + else + this.outputs[0].label = "?"; +} + +MathRand.prototype.onGetInputs = function() { + return [["min","number"],["max","number"]]; +} + +LiteGraph.registerNodeType("math/rand", MathRand); + +//Math clamp +function MathClamp() +{ + this.addInput("in","number"); + this.addOutput("out","number"); + this.size = [60,20]; + this.addProperty( "min", 0 ); + this.addProperty( "max", 1 ); +} + +MathClamp.title = "Clamp"; +MathClamp.desc = "Clamp number between min and max"; +MathClamp.filter = "shader"; + +MathClamp.prototype.onExecute = function() +{ + var v = this.getInputData(0); + if(v == null) return; + v = Math.max(this.properties.min,v); + v = Math.min(this.properties.max,v); + this.setOutputData(0, v ); +} + +MathClamp.prototype.getCode = function(lang) +{ + var code = ""; + if(this.isInputConnected(0)) + code += "clamp({{0}}," + this.properties.min + "," + this.properties.max + ")"; + return code; +} + +LiteGraph.registerNodeType("math/clamp", MathClamp ); + + + +//Math ABS +function MathLerp() +{ + this.properties = { f: 0.5 }; + this.addInput("A","number"); + this.addInput("B","number"); + + this.addOutput("out","number"); +} + +MathLerp.title = "Lerp"; +MathLerp.desc = "Linear Interpolation"; + +MathLerp.prototype.onExecute = function() +{ + var v1 = this.getInputData(0); + if(v1 == null) + v1 = 0; + var v2 = this.getInputData(1); + if(v2 == null) + v2 = 0; + + var f = this.properties.f; + + var _f = this.getInputData(2); + if(_f !== undefined) + f = _f; + + this.setOutputData(0, v1 * (1-f) + v2 * f ); +} + +MathLerp.prototype.onGetInputs = function() +{ + return [["f","number"]]; +} + +LiteGraph.registerNodeType("math/lerp", MathLerp); + + + +//Math ABS +function MathAbs() +{ + this.addInput("in","number"); + this.addOutput("out","number"); + this.size = [60,20]; +} + +MathAbs.title = "Abs"; +MathAbs.desc = "Absolute"; + +MathAbs.prototype.onExecute = function() +{ + var v = this.getInputData(0); + if(v == null) return; + this.setOutputData(0, Math.abs(v) ); +} + +LiteGraph.registerNodeType("math/abs", MathAbs); + + +//Math Floor +function MathFloor() +{ + this.addInput("in","number"); + this.addOutput("out","number"); + this.size = [60,20]; +} + +MathFloor.title = "Floor"; +MathFloor.desc = "Floor number to remove fractional part"; + +MathFloor.prototype.onExecute = function() +{ + var v = this.getInputData(0); + if(v == null) return; + this.setOutputData(0, Math.floor(v) ); +} + +LiteGraph.registerNodeType("math/floor", MathFloor ); + + +//Math frac +function MathFrac() +{ + this.addInput("in","number"); + this.addOutput("out","number"); + this.size = [60,20]; +} + +MathFrac.title = "Frac"; +MathFrac.desc = "Returns fractional part"; + +MathFrac.prototype.onExecute = function() +{ + var v = this.getInputData(0); + if(v == null) + return; + this.setOutputData(0, v%1 ); +} + +LiteGraph.registerNodeType("math/frac",MathFrac); + + +//Math Floor +function MathSmoothStep() +{ + this.addInput("in","number"); + this.addOutput("out","number"); + this.size = [60,20]; + this.properties = { A: 0, B: 1 }; +} + +MathSmoothStep.title = "Smoothstep"; +MathSmoothStep.desc = "Smoothstep"; + +MathSmoothStep.prototype.onExecute = function() +{ + var v = this.getInputData(0); + if(v === undefined) + return; + + var edge0 = this.properties.A; + var edge1 = this.properties.B; + + // Scale, bias and saturate x to 0..1 range + v = Math.clamp((v - edge0)/(edge1 - edge0), 0.0, 1.0); + // Evaluate polynomial + v = v*v*(3 - 2*v); + + this.setOutputData(0, v ); +} + +LiteGraph.registerNodeType("math/smoothstep", MathSmoothStep ); + +//Math scale +function MathScale() +{ + this.addInput("in","number",{label:""}); + this.addOutput("out","number",{label:""}); + this.size = [60,20]; + this.addProperty( "factor", 1 ); +} + +MathScale.title = "Scale"; +MathScale.desc = "v * factor"; + +MathScale.prototype.onExecute = function() +{ + var value = this.getInputData(0); + if(value != null) + this.setOutputData(0, value * this.properties.factor ); +} + +LiteGraph.registerNodeType("math/scale", MathScale ); + + +//Math Average +function MathAverageFilter() +{ + this.addInput("in","number"); + this.addOutput("out","number"); + this.size = [60,20]; + this.addProperty( "samples", 10 ); + this._values = new Float32Array(10); + this._current = 0; +} + +MathAverageFilter.title = "Average"; +MathAverageFilter.desc = "Average Filter"; + +MathAverageFilter.prototype.onExecute = function() +{ + var v = this.getInputData(0); + if(v == null) + v = 0; + + var num_samples = this._values.length; + + this._values[ this._current % num_samples ] = v; + this._current += 1; + if(this._current > num_samples) + this._current = 0; + + var avr = 0; + for(var i = 0; i < num_samples; ++i) + avr += this._values[i]; + + this.setOutputData( 0, avr / num_samples ); +} + +MathAverageFilter.prototype.onPropertyChanged = function( name, value ) +{ + if(value < 1) + value = 1; + this.properties.samples = Math.round(value); + var old = this._values; + + this._values = new Float32Array( this.properties.samples ); + if(old.length <= this._values.length ) + this._values.set(old); + else + this._values.set( old.subarray( 0, this._values.length ) ); +} + +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() +{ + this.addInput("A","number"); + this.addInput("B","number"); + this.addOutput("=","number"); + this.addProperty( "A", 1 ); + this.addProperty( "B", 1 ); + this.addProperty( "OP", "+", "enum", { values: MathOperation.values } ); +} + +MathOperation.values = ["+","-","*","/","%","^"]; + +MathOperation.title = "Operation"; +MathOperation.desc = "Easy math operators"; +MathOperation["@OP"] = { type:"enum", title: "operation", values: MathOperation.values }; + +MathOperation.prototype.getTitle = function() +{ + return "A " + this.properties.OP + " B"; +} + +MathOperation.prototype.setValue = function(v) +{ + if( typeof(v) == "string") v = parseFloat(v); + this.properties["value"] = v; +} + +MathOperation.prototype.onExecute = function() +{ + var A = this.getInputData(0); + var B = this.getInputData(1); + if(A!=null) + this.properties["A"] = A; + else + A = this.properties["A"]; + + if(B!=null) + this.properties["B"] = B; + else + B = this.properties["B"]; + + var result = 0; + switch(this.properties.OP) + { + case '+': result = A+B; break; + case '-': result = A-B; break; + case 'x': + case 'X': + case '*': result = A*B; break; + case '/': result = A/B; break; + case '%': result = A%B; break; + case '^': result = Math.pow(A,B); break; + default: + console.warn("Unknown operation: " + this.properties.OP); + } + this.setOutputData(0, result ); +} + +MathOperation.prototype.onDrawBackground = function(ctx) +{ + if(this.flags.collapsed) + return; + + ctx.font = "40px Arial"; + ctx.fillStyle = "#CCC"; + ctx.textAlign = "center"; + ctx.fillText(this.properties.OP, this.size[0] * 0.5, this.size[1] * 0.35 + LiteGraph.NODE_TITLE_HEIGHT ); + ctx.textAlign = "left"; +} + +LiteGraph.registerNodeType("math/operation", MathOperation ); + + +//Math compare +function MathCompare() +{ + this.addInput( "A","number" ); + this.addInput( "B","number" ); + this.addOutput("A==B","boolean"); + this.addOutput("A!=B","boolean"); + this.addProperty( "A", 0 ); + this.addProperty( "B", 0 ); +} + +MathCompare.title = "Compare"; +MathCompare.desc = "compares between two values"; + +MathCompare.prototype.onExecute = function() +{ + var A = this.getInputData(0); + var B = this.getInputData(1); + if(A !== undefined) + this.properties["A"] = A; + else + A = this.properties["A"]; + + if(B !== undefined) + this.properties["B"] = B; + else + B = this.properties["B"]; + + for(var i = 0, l = this.outputs.length; i < l; ++i) + { + var output = this.outputs[i]; + if(!output.links || !output.links.length) + continue; + switch( output.name ) + { + case "A==B": value = A==B; break; + case "A!=B": value = A!=B; break; + case "A>B": value = A>B; break; + case "A=B": value = A>=B; break; + } + this.setOutputData(i, value ); + } +}; + +MathCompare.prototype.onGetOutputs = function() +{ + return [["A==B","boolean"],["A!=B","boolean"],["A>B","boolean"],["A=B","boolean"],["A<=B","boolean"]]; +} + +LiteGraph.registerNodeType("math/compare",MathCompare); + +function MathCondition() +{ + this.addInput("A","number"); + this.addInput("B","number"); + this.addOutput("out","boolean"); + this.addProperty( "A", 1 ); + this.addProperty( "B", 1 ); + this.addProperty( "OP", ">", "string", { values: MathCondition.values } ); + + this.size = [60,40]; +} + +MathCondition.values = [">","<","==","!=","<=",">="]; +MathCondition["@OP"] = { type:"enum", title: "operation", values: MathCondition.values }; + +MathCondition.title = "Condition"; +MathCondition.desc = "evaluates condition between A and B"; + +MathCondition.prototype.onExecute = function() +{ + var A = this.getInputData(0); + if(A === undefined) + A = this.properties.A; + else + this.properties.A = A; + + var B = this.getInputData(1); + if(B === undefined) + B = this.properties.B; + else + this.properties.B = B; + + var result = true; + switch(this.properties.OP) + { + case ">": result = A>B; break; + case "<": result = A=": result = A>=B; break; + } + + this.setOutputData(0, result ); +} + +LiteGraph.registerNodeType("math/condition", MathCondition); + + +function MathAccumulate() +{ + this.addInput("inc","number"); + this.addOutput("total","number"); + this.addProperty( "increment", 1 ); + this.addProperty( "value", 0 ); +} + +MathAccumulate.title = "Accumulate"; +MathAccumulate.desc = "Increments a value every time"; + +MathAccumulate.prototype.onExecute = function() +{ + if(this.properties.value === null) + this.properties.value = 0; + + var inc = this.getInputData(0); + if(inc !== null) + this.properties.value += inc; + else + this.properties.value += this.properties.increment; + this.setOutputData(0, this.properties.value ); +} + +LiteGraph.registerNodeType("math/accumulate", MathAccumulate); + +//Math Trigonometry +function MathTrigonometry() +{ + this.addInput("v","number"); + this.addOutput("sin","number"); + + this.addProperty( "amplitude", 1 ); + this.addProperty( "offset", 0 ); + this.bgImageUrl = "nodes/imgs/icon-sin.png"; +} + +MathTrigonometry.title = "Trigonometry"; +MathTrigonometry.desc = "Sin Cos Tan"; +MathTrigonometry.filter = "shader"; + +MathTrigonometry.prototype.onExecute = function() +{ + var v = this.getInputData(0); + if(v == null) + v = 0; + var amplitude = this.properties["amplitude"]; + var slot = this.findInputSlot("amplitude"); + if(slot != -1) + amplitude = this.getInputData(slot); + var offset = this.properties["offset"]; + slot = this.findInputSlot("offset"); + if(slot != -1) + offset = this.getInputData(slot); + + for(var i = 0, l = this.outputs.length; i < l; ++i) + { + var output = this.outputs[i]; + switch( output.name ) + { + case "sin": value = Math.sin(v); break; + case "cos": value = Math.cos(v); break; + case "tan": value = Math.tan(v); break; + case "asin": value = Math.asin(v); break; + case "acos": value = Math.acos(v); break; + case "atan": value = Math.atan(v); break; + } + this.setOutputData(i, amplitude * value + offset); + } +} + +MathTrigonometry.prototype.onGetInputs = function() +{ + return [["v","number"],["amplitude","number"],["offset","number"]]; +} + + +MathTrigonometry.prototype.onGetOutputs = function() +{ + return [["sin","number"],["cos","number"],["tan","number"],["asin","number"],["acos","number"],["atan","number"]]; +} + + +LiteGraph.registerNodeType("math/trigonometry", MathTrigonometry ); + + + +//math library for safe math operations without eval +if(typeof(math) != undefined) +{ + function MathFormula() + { + this.addInputs("x","number"); + this.addInputs("y","number"); + this.addOutputs("","number"); + this.properties = {x:1.0, y:1.0, formula:"x+y"}; + } + + MathFormula.title = "Formula"; + MathFormula.desc = "Compute safe formula"; + + MathFormula.prototype.onExecute = function() + { + var x = this.getInputData(0); + var y = this.getInputData(1); + if(x != null) + this.properties["x"] = x; + else + x = this.properties["x"]; + + if(y!=null) + this.properties["y"] = y; + else + y = this.properties["y"]; + + var f = this.properties["formula"]; + var value = math.eval(f,{x:x,y:y,T: this.graph.globaltime }); + this.setOutputData(0, value ); + } + + MathFormula.prototype.onDrawBackground = function() + { + var f = this.properties["formula"]; + this.outputs[0].label = f; + } + + MathFormula.prototype.onGetOutputs = function() + { + return [["A-B","number"],["A*B","number"],["A/B","number"]]; + } + + LiteGraph.registerNodeType("math/formula", MathFormula ); +} + + +function Math3DVec2ToXYZ() +{ + this.addInput("vec2","vec2"); + this.addOutput("x","number"); + this.addOutput("y","number"); +} + +Math3DVec2ToXYZ.title = "Vec2->XY"; +Math3DVec2ToXYZ.desc = "vector 2 to components"; + +Math3DVec2ToXYZ.prototype.onExecute = function() +{ + var v = this.getInputData(0); + if(v == null) return; + + this.setOutputData( 0, v[0] ); + this.setOutputData( 1, v[1] ); +} + +LiteGraph.registerNodeType("math3d/vec2-to-xyz", Math3DVec2ToXYZ ); + + +function Math3DXYToVec2() +{ + this.addInputs([["x","number"],["y","number"]]); + this.addOutput("vec2","vec2"); + this.properties = {x:0, y:0}; + this._data = new Float32Array(2); +} + +Math3DXYToVec2.title = "XY->Vec2"; +Math3DXYToVec2.desc = "components to vector2"; + +Math3DXYToVec2.prototype.onExecute = function() +{ + var x = this.getInputData(0); + if(x == null) x = this.properties.x; + var y = this.getInputData(1); + if(y == null) y = this.properties.y; + + var data = this._data; + data[0] = x; + data[1] = y; + + this.setOutputData( 0, data ); +} + +LiteGraph.registerNodeType("math3d/xy-to-vec2", Math3DXYToVec2 ); + + + + +function Math3DVec3ToXYZ() +{ + this.addInput("vec3","vec3"); + this.addOutput("x","number"); + this.addOutput("y","number"); + this.addOutput("z","number"); +} + +Math3DVec3ToXYZ.title = "Vec3->XYZ"; +Math3DVec3ToXYZ.desc = "vector 3 to components"; + +Math3DVec3ToXYZ.prototype.onExecute = function() +{ + var v = this.getInputData(0); + if(v == null) return; + + this.setOutputData( 0, v[0] ); + this.setOutputData( 1, v[1] ); + this.setOutputData( 2, v[2] ); +} + +LiteGraph.registerNodeType("math3d/vec3-to-xyz", Math3DVec3ToXYZ ); + + +function Math3DXYZToVec3() +{ + this.addInputs([["x","number"],["y","number"],["z","number"]]); + this.addOutput("vec3","vec3"); + this.properties = {x:0, y:0, z:0}; + this._data = new Float32Array(3); +} + +Math3DXYZToVec3.title = "XYZ->Vec3"; +Math3DXYZToVec3.desc = "components to vector3"; + +Math3DXYZToVec3.prototype.onExecute = function() +{ + var x = this.getInputData(0); + if(x == null) x = this.properties.x; + var y = this.getInputData(1); + if(y == null) y = this.properties.y; + var z = this.getInputData(2); + if(z == null) z = this.properties.z; + + var data = this._data; + data[0] = x; + data[1] = y; + data[2] = z; + + this.setOutputData( 0, data ); +} + +LiteGraph.registerNodeType("math3d/xyz-to-vec3", Math3DXYZToVec3 ); + + + +function Math3DVec4ToXYZW() +{ + this.addInput("vec4","vec4"); + this.addOutput("x","number"); + this.addOutput("y","number"); + this.addOutput("z","number"); + this.addOutput("w","number"); +} + +Math3DVec4ToXYZW.title = "Vec4->XYZW"; +Math3DVec4ToXYZW.desc = "vector 4 to components"; + +Math3DVec4ToXYZW.prototype.onExecute = function() +{ + var v = this.getInputData(0); + if(v == null) return; + + this.setOutputData( 0, v[0] ); + this.setOutputData( 1, v[1] ); + this.setOutputData( 2, v[2] ); + this.setOutputData( 3, v[3] ); +} + +LiteGraph.registerNodeType("math3d/vec4-to-xyzw", Math3DVec4ToXYZW ); + + +function Math3DXYZWToVec4() +{ + this.addInputs([["x","number"],["y","number"],["z","number"],["w","number"]]); + this.addOutput("vec4","vec4"); + this.properties = {x:0, y:0, z:0, w:0}; + this._data = new Float32Array(4); +} + +Math3DXYZWToVec4.title = "XYZW->Vec4"; +Math3DXYZWToVec4.desc = "components to vector4"; + +Math3DXYZWToVec4.prototype.onExecute = function() +{ + var x = this.getInputData(0); + if(x == null) x = this.properties.x; + var y = this.getInputData(1); + if(y == null) y = this.properties.y; + var z = this.getInputData(2); + if(z == null) z = this.properties.z; + var w = this.getInputData(3); + if(w == null) w = this.properties.w; + + var data = this._data; + data[0] = x; + data[1] = y; + data[2] = z; + data[3] = w; + + this.setOutputData( 0, data ); +} + +LiteGraph.registerNodeType("math3d/xyzw-to-vec4", Math3DXYZWToVec4 ); + + + + +//if glMatrix is installed... +if(global.glMatrix) +{ + + function Math3DQuaternion() + { + this.addOutput("quat","quat"); + this.properties = { x:0, y:0, z:0, w: 1 }; + this._value = quat.create(); + } + + Math3DQuaternion.title = "Quaternion"; + Math3DQuaternion.desc = "quaternion"; + + Math3DQuaternion.prototype.onExecute = function() + { + this._value[0] = this.properties.x; + this._value[1] = this.properties.y; + this._value[2] = this.properties.z; + this._value[3] = this.properties.w; + this.setOutputData( 0, this._value ); + } + + LiteGraph.registerNodeType("math3d/quaternion", Math3DQuaternion ); + + + function Math3DRotation() + { + this.addInputs([["degrees","number"],["axis","vec3"]]); + this.addOutput("quat","quat"); + this.properties = { angle:90.0, axis: vec3.fromValues(0,1,0) }; + + this._value = quat.create(); + } + + Math3DRotation.title = "Rotation"; + Math3DRotation.desc = "quaternion rotation"; + + Math3DRotation.prototype.onExecute = function() + { + var angle = this.getInputData(0); + if(angle == null) angle = this.properties.angle; + var axis = this.getInputData(1); + if(axis == null) axis = this.properties.axis; + + var R = quat.setAxisAngle( this._value, axis, angle * 0.0174532925 ); + this.setOutputData( 0, R ); + } + + + LiteGraph.registerNodeType("math3d/rotation", Math3DRotation ); + + + //Math3D rotate vec3 + function Math3DRotateVec3() + { + this.addInputs([["vec3","vec3"],["quat","quat"]]); + this.addOutput("result","vec3"); + this.properties = { vec: [0,0,1] }; + } + + Math3DRotateVec3.title = "Rot. Vec3"; + Math3DRotateVec3.desc = "rotate a point"; + + Math3DRotateVec3.prototype.onExecute = function() + { + var vec = this.getInputData(0); + if(vec == null) vec = this.properties.vec; + var quat = this.getInputData(1); + if(quat == null) + this.setOutputData(vec); + else + this.setOutputData( 0, vec3.transformQuat( vec3.create(), vec, quat ) ); + } + + LiteGraph.registerNodeType("math3d/rotate_vec3", Math3DRotateVec3); + + + + function Math3DMultQuat() + { + this.addInputs( [["A","quat"],["B","quat"]] ); + this.addOutput( "A*B","quat" ); + + this._value = quat.create(); + } + + Math3DMultQuat.title = "Mult. Quat"; + Math3DMultQuat.desc = "rotate quaternion"; + + Math3DMultQuat.prototype.onExecute = function() + { + var A = this.getInputData(0); + if(A == null) return; + var B = this.getInputData(1); + if(B == null) return; + + var R = quat.multiply( this._value, A, B ); + this.setOutputData( 0, R ); + } + + LiteGraph.registerNodeType("math3d/mult-quat", Math3DMultQuat ); + + + function Math3DQuatSlerp() + { + this.addInputs( [["A","quat"],["B","quat"],["factor","number"]] ); + this.addOutput( "slerp","quat" ); + this.addProperty( "factor", 0.5 ); + + this._value = quat.create(); + } + + Math3DQuatSlerp.title = "Quat Slerp"; + Math3DQuatSlerp.desc = "quaternion spherical interpolation"; + + Math3DQuatSlerp.prototype.onExecute = function() + { + var A = this.getInputData(0); + if(A == null) + return; + var B = this.getInputData(1); + if(B == null) + return; + var factor = this.properties.factor; + if( this.getInputData(2) != null ) + factor = this.getInputData(2); + + var R = quat.slerp( this._value, A, B, factor ); + this.setOutputData( 0, R ); + } + + LiteGraph.registerNodeType("math3d/quat-slerp", Math3DQuatSlerp ); + +} //glMatrix + +})(this); +(function(global){ +var LiteGraph = global.LiteGraph; + +function Selector() +{ + this.addInput("sel","boolean"); + this.addOutput("value","number"); + this.properties = { A:0, B:1 }; + this.size = [60,20]; +} + +Selector.title = "Selector"; +Selector.desc = "outputs A if selector is true, B if selector is false"; + +Selector.prototype.onExecute = function() +{ + var cond = this.getInputData(0); + if(cond === undefined) + return; + + for(var i = 1; i < this.inputs.length; i++) + { + var input = this.inputs[i]; + var v = this.getInputData(i); + if(v === undefined) + continue; + this.properties[input.name] = v; + } + + var A = this.properties.A; + var B = this.properties.B; + this.setOutputData(0, cond ? A : B ); +} + +Selector.prototype.onGetInputs = function() { + return [["A",0],["B",0]]; +} + +LiteGraph.registerNodeType("logic/selector", Selector); + +})(this); +(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.addOutput("frame","image"); + this.properties = {"url":""}; +} + +GraphicsImage.title = "Image"; +GraphicsImage.desc = "Image loader"; +GraphicsImage.widgets = [{name:"load",text:"Load",type:"button"}]; + +GraphicsImage.supported_extensions = ["jpg","jpeg","png","gif"]; + +GraphicsImage.prototype.onAdded = function() +{ + if(this.properties["url"] != "" && this.img == null) + { + this.loadImage( this.properties["url"] ); + } +} + +GraphicsImage.prototype.onDrawBackground = function(ctx) +{ + if(this.img && this.size[0] > 5 && this.size[1] > 5) + ctx.drawImage(this.img, 0,0,this.size[0],this.size[1]); +} + + +GraphicsImage.prototype.onExecute = function() +{ + if(!this.img) + this.boxcolor = "#000"; + if(this.img && this.img.width) + this.setOutputData(0,this.img); + else + this.setOutputData(0,null); + if(this.img && this.img.dirty) + this.img.dirty = false; +} + +GraphicsImage.prototype.onPropertyChanged = function(name,value) +{ + this.properties[name] = value; + if (name == "url" && value != "") + this.loadImage(value); + + return true; +} + +GraphicsImage.prototype.loadImage = function( url, callback ) +{ + if(url == "") + { + this.img = null; + return; + } + + this.img = document.createElement("img"); + + if(url.substr(0,4) == "http" && LiteGraph.proxy) + url = LiteGraph.proxy + url.substr( url.indexOf(":") + 3 ); + + this.img.src = url; + this.boxcolor = "#F95"; + var that = this; + this.img.onload = function() + { + if(callback) + callback(this); + that.trace("Image loaded, size: " + that.img.width + "x" + that.img.height ); + this.dirty = true; + that.boxcolor = "#9F9"; + that.setDirtyCanvas(true); + } +} + +GraphicsImage.prototype.onWidget = function(e,widget) +{ + if(widget.name == "load") + { + this.loadImage(this.properties["url"]); + } +} + +GraphicsImage.prototype.onDropFile = function(file) +{ + var that = this; + if(this._url) + URL.revokeObjectURL( this._url ); + this._url = URL.createObjectURL( file ); + this.properties.url = this._url; + this.loadImage( this._url, function(img){ + that.size[1] = (img.height / img.width) * that.size[0]; + }); +} + +LiteGraph.registerNodeType("graphics/image", GraphicsImage); + + + +function ColorPalette() +{ + this.addInput("f","number"); + this.addOutput("Color","color"); + this.properties = {colorA:"#444444",colorB:"#44AAFF",colorC:"#44FFAA",colorD:"#FFFFFF"}; + +} + +ColorPalette.title = "Palette"; +ColorPalette.desc = "Generates a color"; + +ColorPalette.prototype.onExecute = function() +{ + var c = []; + + if (this.properties.colorA != null) + c.push( hex2num( this.properties.colorA ) ); + if (this.properties.colorB != null) + c.push( hex2num( this.properties.colorB ) ); + if (this.properties.colorC != null) + c.push( hex2num( this.properties.colorC ) ); + if (this.properties.colorD != null) + c.push( hex2num( this.properties.colorD ) ); + + var f = this.getInputData(0); + if(f == null) f = 0.5; + if (f > 1.0) + f = 1.0; + else if (f < 0.0) + f = 0.0; + + if(c.length == 0) + return; + + var result = [0,0,0]; + if(f == 0) + result = c[0]; + else if(f == 1) + result = c[ c.length - 1]; + else + { + var pos = (c.length - 1)* f; + var c1 = c[ Math.floor(pos) ]; + var c2 = c[ Math.floor(pos)+1 ]; + var t = pos - Math.floor(pos); + result[0] = c1[0] * (1-t) + c2[0] * (t); + result[1] = c1[1] * (1-t) + c2[1] * (t); + result[2] = c1[2] * (1-t) + c2[2] * (t); + } + + /* + c[0] = 1.0 - Math.abs( Math.sin( 0.1 * reModular.getTime() * Math.PI) ); + c[1] = Math.abs( Math.sin( 0.07 * reModular.getTime() * Math.PI) ); + c[2] = Math.abs( Math.sin( 0.01 * reModular.getTime() * Math.PI) ); + */ + + for(var i in result) + result[i] /= 255; + + this.boxcolor = colorToString(result); + this.setOutputData(0, result); +} + + +LiteGraph.registerNodeType("color/palette", ColorPalette ); + + +function ImageFrame() +{ + this.addInput("","image"); + this.size = [200,200]; +} + +ImageFrame.title = "Frame"; +ImageFrame.desc = "Frame viewerew"; +ImageFrame.widgets = [{name:"resize",text:"Resize box",type:"button"},{name:"view",text:"View Image",type:"button"}]; + + +ImageFrame.prototype.onDrawBackground = function(ctx) +{ + if(this.frame) + ctx.drawImage(this.frame, 0,0,this.size[0],this.size[1]); +} + +ImageFrame.prototype.onExecute = function() +{ + this.frame = this.getInputData(0); + this.setDirtyCanvas(true); +} + +ImageFrame.prototype.onWidget = function(e,widget) +{ + if(widget.name == "resize" && this.frame) + { + var width = this.frame.width; + var height = this.frame.height; + + if(!width && this.frame.videoWidth != null ) + { + width = this.frame.videoWidth; + height = this.frame.videoHeight; + } + + if(width && height) + this.size = [width, height]; + this.setDirtyCanvas(true,true); + } + else if(widget.name == "view") + this.show(); +} + +ImageFrame.prototype.show = function() +{ + //var str = this.canvas.toDataURL("image/png"); + if(showElement && this.frame) + showElement(this.frame); +} + + +LiteGraph.registerNodeType("graphics/frame", ImageFrame ); + + + +/* +LiteGraph.registerNodeType("visualization/graph", { + desc: "Shows a graph of the inputs", + + inputs: [["",0],["",0],["",0],["",0]], + size: [200,200], + properties: {min:-1,max:1,bgColor:"#000"}, + onDrawBackground: function(ctx) + { + var colors = ["#FFF","#FAA","#AFA","#AAF"]; + + if(this.properties.bgColor != null && this.properties.bgColor != "") + { + ctx.fillStyle="#000"; + ctx.fillRect(2,2,this.size[0] - 4, this.size[1]-4); + } + + if(this.data) + { + var min = this.properties["min"]; + var max = this.properties["max"]; + + for(var i in this.data) + { + var data = this.data[i]; + if(!data) continue; + + if(this.getInputInfo(i) == null) continue; + + ctx.strokeStyle = colors[i]; + ctx.beginPath(); + + var d = data.length / this.size[0]; + for(var j = 0; j < data.length; j += d) + { + var value = data[ Math.floor(j) ]; + value = (value - min) / (max - min); + if (value > 1.0) value = 1.0; + else if(value < 0) value = 0; + + if(j == 0) + ctx.moveTo( j / d, (this.size[1] - 5) - (this.size[1] - 10) * value); + else + ctx.lineTo( j / d, (this.size[1] - 5) - (this.size[1] - 10) * value); + } + + ctx.stroke(); + } + } + + //ctx.restore(); + }, + + onExecute: function() + { + if(!this.data) this.data = []; + + for(var i in this.inputs) + { + var value = this.getInputData(i); + + if(typeof(value) == "number") + { + value = value ? value : 0; + if(!this.data[i]) + this.data[i] = []; + this.data[i].push(value); + + if(this.data[i].length > (this.size[1] - 4)) + this.data[i] = this.data[i].slice(1,this.data[i].length); + } + else + this.data[i] = value; + } + + if(this.data.length) + this.setDirtyCanvas(true); + } + }); +*/ + +function ImageFade() +{ + this.addInputs([["img1","image"],["img2","image"],["fade","number"]]); + this.addOutput("","image"); + this.properties = {fade:0.5,width:512,height:512}; +} + +ImageFade.title = "Image fade"; +ImageFade.desc = "Fades between images"; +ImageFade.widgets = [{name:"resizeA",text:"Resize to A",type:"button"},{name:"resizeB",text:"Resize to B",type:"button"}]; + +ImageFade.prototype.onAdded = function() +{ + this.createCanvas(); + var ctx = this.canvas.getContext("2d"); + ctx.fillStyle = "#000"; + ctx.fillRect(0,0,this.properties["width"],this.properties["height"]); +} + +ImageFade.prototype.createCanvas = function() +{ + this.canvas = document.createElement("canvas"); + this.canvas.width = this.properties["width"]; + this.canvas.height = this.properties["height"]; +} + +ImageFade.prototype.onExecute = function() +{ + var ctx = this.canvas.getContext("2d"); + this.canvas.width = this.canvas.width; + + var A = this.getInputData(0); + if (A != null) + { + ctx.drawImage(A,0,0,this.canvas.width, this.canvas.height); + } + + var fade = this.getInputData(2); + if(fade == null) + fade = this.properties["fade"]; + else + this.properties["fade"] = fade; + + ctx.globalAlpha = fade; + var B = this.getInputData(1); + if (B != null) + { + ctx.drawImage(B,0,0,this.canvas.width, this.canvas.height); + } + ctx.globalAlpha = 1.0; + + this.setOutputData(0,this.canvas); + this.setDirtyCanvas(true); +} + +LiteGraph.registerNodeType("graphics/imagefade", ImageFade); + + + +function ImageCrop() +{ + this.addInput("","image"); + this.addOutput("","image"); + this.properties = {width:256,height:256,x:0,y:0,scale:1.0 }; + this.size = [50,20]; +} + +ImageCrop.title = "Crop"; +ImageCrop.desc = "Crop Image"; + +ImageCrop.prototype.onAdded = function() +{ + this.createCanvas(); +} + +ImageCrop.prototype.createCanvas = function() +{ + this.canvas = document.createElement("canvas"); + this.canvas.width = this.properties["width"]; + this.canvas.height = this.properties["height"]; +} + +ImageCrop.prototype.onExecute = function() +{ + var input = this.getInputData(0); + if(!input) + return; + + if(input.width) + { + var ctx = this.canvas.getContext("2d"); + + ctx.drawImage(input, -this.properties["x"],-this.properties["y"], input.width * this.properties["scale"], input.height * this.properties["scale"]); + this.setOutputData(0,this.canvas); + } + else + this.setOutputData(0,null); +} + +ImageCrop.prototype.onDrawBackground = function(ctx) +{ + if(this.flags.collapsed) + return; + if(this.canvas) + ctx.drawImage( this.canvas, 0,0,this.canvas.width,this.canvas.height, 0,0, this.size[0], this.size[1] ); +} + +ImageCrop.prototype.onPropertyChanged = function(name,value) +{ + this.properties[name] = value; + + if(name == "scale") + { + this.properties[name] = parseFloat(value); + if(this.properties[name] == 0) + { + this.trace("Error in scale"); + this.properties[name] = 1.0; + } + } + else + this.properties[name] = parseInt(value); + + this.createCanvas(); + + return true; +} + +LiteGraph.registerNodeType("graphics/cropImage", ImageCrop ); + + +function ImageVideo() +{ + this.addInput("t","number"); + this.addOutputs([["frame","image"],["t","number"],["d","number"]]); + this.properties = { url:"", use_proxy: true }; +} + +ImageVideo.title = "Video"; +ImageVideo.desc = "Video playback"; +ImageVideo.widgets = [{name:"play",text:"PLAY",type:"minibutton"},{name:"stop",text:"STOP",type:"minibutton"},{name:"demo",text:"Demo video",type:"button"},{name:"mute",text:"Mute video",type:"button"}]; + +ImageVideo.prototype.onExecute = function() +{ + if(!this.properties.url) + return; + + if(this.properties.url != this._video_url) + this.loadVideo(this.properties.url); + + if(!this._video || this._video.width == 0) + return; + + var t = this.getInputData(0); + if(t && t >= 0 && t <= 1.0) + { + this._video.currentTime = t * this._video.duration; + this._video.pause(); + } + + this._video.dirty = true; + this.setOutputData(0,this._video); + this.setOutputData(1,this._video.currentTime); + this.setOutputData(2,this._video.duration); + this.setDirtyCanvas(true); +} + +ImageVideo.prototype.onStart = function() +{ + this.play(); +} + +ImageVideo.prototype.onStop = function() +{ + this.stop(); +} + +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"; + + this._video.muted = true; + this._video.autoplay = true; + + var that = this; + this._video.addEventListener("loadedmetadata",function(e) { + //onload + that.trace("Duration: " + this.duration + " seconds"); + that.trace("Size: " + this.videoWidth + "," + this.videoHeight); + that.setDirtyCanvas(true); + this.width = this.videoWidth; + this.height = this.videoHeight; + }); + this._video.addEventListener("progress",function(e) { + //onload + //that.trace("loading..."); + }); + this._video.addEventListener("error",function(e) { + console.log("Error loading video: " + this.src); + that.trace("Error loading video: " + this.src); + if (this.error) { + switch (this.error.code) { + case this.error.MEDIA_ERR_ABORTED: + that.trace("You stopped the video."); + break; + case this.error.MEDIA_ERR_NETWORK: + that.trace("Network error - please try again later."); + break; + case this.error.MEDIA_ERR_DECODE: + that.trace("Video is broken.."); + break; + case this.error.MEDIA_ERR_SRC_NOT_SUPPORTED: + that.trace("Sorry, your browser can't play this video."); + break; + } + } + }); + + this._video.addEventListener("ended",function(e) { + that.trace("Ended."); + this.play(); //loop + }); + + //document.body.appendChild(this.video); +} + +ImageVideo.prototype.onPropertyChanged = function(name,value) +{ + this.properties[name] = value; + if (name == "url" && value != "") + this.loadVideo(value); + + return true; +} + +ImageVideo.prototype.play = function() +{ + if(this._video) + this._video.play(); +} + +ImageVideo.prototype.playPause = function() +{ + if(!this._video) + return; + if(this._video.paused) + this.play(); + else + this.pause(); +} + +ImageVideo.prototype.stop = function() +{ + if(!this._video) + return; + this._video.pause(); + this._video.currentTime = 0; +} + +ImageVideo.prototype.pause = function() +{ + if(!this._video) + return; + this.trace("Video paused"); + this._video.pause(); +} + +ImageVideo.prototype.onWidget = function(e,widget) +{ + /* + if(widget.name == "demo") + { + this.loadVideo(); + } + else if(widget.name == "play") + { + if(this._video) + this.playPause(); + } + if(widget.name == "stop") + { + this.stop(); + } + else if(widget.name == "mute") + { + if(this._video) + this._video.muted = !this._video.muted; + } + */ +} + +LiteGraph.registerNodeType("graphics/video", ImageVideo ); + + +// Texture Webcam ***************************************** +function ImageWebcam() +{ + this.addOutput("Webcam","image"); + this.properties = {}; +} + +ImageWebcam.title = "Webcam"; +ImageWebcam.desc = "Webcam image"; + + +ImageWebcam.prototype.openStream = function() +{ + //Vendor prefixes hell + navigator.getUserMedia = (navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia); + window.URL = window.URL || window.webkitURL; + + if (!navigator.getUserMedia) { + //console.log('getUserMedia() is not supported in your browser, use chrome and enable WebRTC from about://flags'); + return; + } + + this._waiting_confirmation = true; + + // Not showing vendor prefixes. + navigator.getUserMedia({video: true}, this.streamReady.bind(this), onFailSoHard); + + var that = this; + function onFailSoHard(e) { + console.log('Webcam rejected', e); + that._webcam_stream = false; + that.box_color = "red"; + }; +} + +ImageWebcam.prototype.onRemoved = function() +{ + if(this._webcam_stream) + { + this._webcam_stream.stop(); + this._webcam_stream = null; + this._video = null; + } +} + +ImageWebcam.prototype.streamReady = function(localMediaStream) +{ + this._webcam_stream = localMediaStream; + //this._waiting_confirmation = false; + + var video = this._video; + if(!video) + { + video = document.createElement("video"); + video.autoplay = true; + video.src = window.URL.createObjectURL(localMediaStream); + this._video = video; + //document.body.appendChild( video ); //debug + //when video info is loaded (size and so) + video.onloadedmetadata = function(e) { + // Ready to go. Do some stuff. + console.log(e); + }; + } +}, + +ImageWebcam.prototype.onExecute = function() +{ + if(this._webcam_stream == null && !this._waiting_confirmation) + this.openStream(); + + if(!this._video || !this._video.videoWidth) return; + + this._video.width = this._video.videoWidth; + this._video.height = this._video.videoHeight; + this.setOutputData(0, this._video); +} + +ImageWebcam.prototype.getExtraMenuOptions = function(graphcanvas) +{ + var that = this; + var txt = !that.properties.show ? "Show Frame" : "Hide Frame"; + return [ {content: txt, callback: + function() { + that.properties.show = !that.properties.show; + } + }]; +} + +ImageWebcam.prototype.onDrawBackground = function(ctx) +{ + if(this.flags.collapsed || this.size[1] <= 20 || !this.properties.show) + return; + + if(!this._video) + return; + + //render to graph canvas + ctx.save(); + ctx.drawImage(this._video, 0, 0, this.size[0], this.size[1]); + ctx.restore(); +} + +LiteGraph.registerNodeType("graphics/webcam", ImageWebcam ); + + +})(this); + +(function(global){ +var LiteGraph = global.LiteGraph; + +//Works with Litegl.js to create WebGL nodes +global.LGraphTexture = null; + +if(typeof(GL) != "undefined") +{ + function LGraphTexture() + { + this.addOutput("Texture","Texture"); + this.properties = { name:"", filter: true }; + this.size = [LGraphTexture.image_preview_size, LGraphTexture.image_preview_size]; + } + + global.LGraphTexture = LGraphTexture; + + LGraphTexture.title = "Texture"; + LGraphTexture.desc = "Texture"; + LGraphTexture.widgets_info = {"name": { widget:"texture"}, "filter": { widget:"checkbox"} }; + + //REPLACE THIS TO INTEGRATE WITH YOUR FRAMEWORK + LGraphTexture.loadTextureCallback = null; //function in charge of loading textures when not present in the container + LGraphTexture.image_preview_size = 256; + + //flags to choose output texture type + LGraphTexture.PASS_THROUGH = 1; //do not apply FX + LGraphTexture.COPY = 2; //create new texture with the same properties as the origin texture + LGraphTexture.LOW = 3; //create new texture with low precision (byte) + LGraphTexture.HIGH = 4; //create new texture with high precision (half-float) + LGraphTexture.REUSE = 5; //reuse input texture + LGraphTexture.DEFAULT = 2; + + LGraphTexture.MODE_VALUES = { + "pass through": LGraphTexture.PASS_THROUGH, + "copy": LGraphTexture.COPY, + "low": LGraphTexture.LOW, + "high": LGraphTexture.HIGH, + "reuse": LGraphTexture.REUSE, + "default": LGraphTexture.DEFAULT + }; + + //returns the container where all the loaded textures are stored (overwrite if you have a Resources Manager) + LGraphTexture.getTexturesContainer = function() + { + return gl.textures; + } + + //process the loading of a texture (overwrite it if you have a Resources Manager) + LGraphTexture.loadTexture = function(name, options) + { + options = options || {}; + var url = name; + if(url.substr(0,7) == "http://") + { + if(LiteGraph.proxy) //proxy external files + url = LiteGraph.proxy + url.substr(7); + } + + var container = LGraphTexture.getTexturesContainer(); + var tex = container[ name ] = GL.Texture.fromURL(url, options); + return tex; + } + + LGraphTexture.getTexture = function(name) + { + var container = this.getTexturesContainer(); + + if(!container) + throw("Cannot load texture, container of textures not found"); + + var tex = container[ name ]; + if(!tex && name && name[0] != ":") + return this.loadTexture(name); + + return tex; + } + + //used to compute the appropiate output texture + LGraphTexture.getTargetTexture = function( origin, target, mode ) + { + if(!origin) + throw("LGraphTexture.getTargetTexture expects a reference texture"); + + var tex_type = null; + + switch(mode) + { + case LGraphTexture.LOW: tex_type = gl.UNSIGNED_BYTE; break; + case LGraphTexture.HIGH: tex_type = gl.HIGH_PRECISION_FORMAT; break; + case LGraphTexture.REUSE: return origin; break; + case LGraphTexture.COPY: + default: tex_type = origin ? origin.type : gl.UNSIGNED_BYTE; break; + } + + if(!target || target.width != origin.width || target.height != origin.height || target.type != tex_type ) + target = new GL.Texture( origin.width, origin.height, { type: tex_type, format: gl.RGBA, filter: gl.LINEAR }); + + return target; + } + + + LGraphTexture.getTextureType = function( precision, ref_texture ) + { + var type = ref_texture ? ref_texture.type : gl.UNSIGNED_BYTE; + switch( precision ) + { + case LGraphTexture.HIGH: type = gl.HIGH_PRECISION_FORMAT; break; + case LGraphTexture.LOW: type = gl.UNSIGNED_BYTE; break; + //no default + } + return type; + } + + LGraphTexture.getNoiseTexture = function() + { + if(this._noise_texture) + return this._noise_texture; + + var noise = new Uint8Array(512*512*4); + for(var i = 0; i < 512*512*4; ++i) + noise[i] = Math.random() * 255; + + var texture = GL.Texture.fromMemory(512,512,noise,{ format: gl.RGBA, wrap: gl.REPEAT, filter: gl.NEAREST }); + this._noise_texture = texture; + return texture; + } + + LGraphTexture.prototype.onDropFile = function(data, filename, file) + { + if(!data) + { + this._drop_texture = null; + this.properties.name = ""; + } + else + { + var texture = null; + if( typeof(data) == "string" ) + texture = GL.Texture.fromURL( data ); + else if( filename.toLowerCase().indexOf(".dds") != -1 ) + texture = GL.Texture.fromDDSInMemory(data); + else + { + var blob = new Blob([file]); + var url = URL.createObjectURL(blob); + texture = GL.Texture.fromURL( url ); + } + + this._drop_texture = texture; + this.properties.name = filename; + } + } + + LGraphTexture.prototype.getExtraMenuOptions = function(graphcanvas) + { + var that = this; + if(!this._drop_texture) + return; + return [ {content:"Clear", callback: + function() { + that._drop_texture = null; + that.properties.name = ""; + } + }]; + } + + LGraphTexture.prototype.onExecute = function() + { + var tex = null; + if(this.isOutputConnected(1)) + tex = this.getInputData(0); + + if(!tex && this._drop_texture) + tex = this._drop_texture; + + if(!tex && this.properties.name) + tex = LGraphTexture.getTexture( this.properties.name ); + + if(!tex) + return; + + this._last_tex = tex; + + if(this.properties.filter === false) + tex.setParameter( gl.TEXTURE_MAG_FILTER, gl.NEAREST ); + else + tex.setParameter( gl.TEXTURE_MAG_FILTER, gl.LINEAR ); + + this.setOutputData(0, tex); + + for(var i = 1; i < this.outputs.length; i++) + { + var output = this.outputs[i]; + if(!output) + continue; + var v = null; + if(output.name == "width") + v = tex.width; + else if(output.name == "height") + v = tex.height; + else if(output.name == "aspect") + v = tex.width / tex.height; + this.setOutputData(i, v); + } + } + + LGraphTexture.prototype.onResourceRenamed = function(old_name,new_name) + { + if(this.properties.name == old_name) + this.properties.name = new_name; + } + + LGraphTexture.prototype.onDrawBackground = function(ctx) + { + if( this.flags.collapsed || this.size[1] <= 20 ) + return; + + if( this._drop_texture && ctx.webgl ) + { + ctx.drawImage( this._drop_texture, 0,0,this.size[0],this.size[1]); + //this._drop_texture.renderQuad(this.pos[0],this.pos[1],this.size[0],this.size[1]); + return; + } + + + //Different texture? then get it from the GPU + if(this._last_preview_tex != this._last_tex) + { + if(ctx.webgl) + { + this._canvas = this._last_tex; + } + else + { + var tex_canvas = LGraphTexture.generateLowResTexturePreview(this._last_tex); + if(!tex_canvas) + return; + + this._last_preview_tex = this._last_tex; + this._canvas = cloneCanvas(tex_canvas); + } + } + + if(!this._canvas) + return; + + //render to graph canvas + ctx.save(); + if(!ctx.webgl) //reverse image + { + ctx.translate(0,this.size[1]); + ctx.scale(1,-1); + } + ctx.drawImage(this._canvas,0,0,this.size[0],this.size[1]); + ctx.restore(); + } + + + //very slow, used at your own risk + LGraphTexture.generateLowResTexturePreview = function(tex) + { + if(!tex) + return null; + + var size = LGraphTexture.image_preview_size; + var temp_tex = tex; + + if(tex.format == gl.DEPTH_COMPONENT) + return null; //cannot generate from depth + + //Generate low-level version in the GPU to speed up + if(tex.width > size || tex.height > size) + { + temp_tex = this._preview_temp_tex; + if(!this._preview_temp_tex) + { + temp_tex = new GL.Texture(size,size, { minFilter: gl.NEAREST }); + this._preview_temp_tex = temp_tex; + } + + //copy + tex.copyTo(temp_tex); + tex = temp_tex; + } + + //create intermediate canvas with lowquality version + var tex_canvas = this._preview_canvas; + if(!tex_canvas) + { + tex_canvas = createCanvas(size,size); + this._preview_canvas = tex_canvas; + } + + if(temp_tex) + temp_tex.toCanvas(tex_canvas); + return tex_canvas; + } + + LGraphTexture.prototype.getResources = function(res) + { + res[ this.properties.name ] = GL.Texture; + return res; + } + + LGraphTexture.prototype.onGetInputs = function() + { + return [["in","Texture"]]; + } + + + LGraphTexture.prototype.onGetOutputs = function() + { + return [["width","number"],["height","number"],["aspect","number"]]; + } + + LiteGraph.registerNodeType("texture/texture", LGraphTexture ); + + //************************** + function LGraphTexturePreview() + { + this.addInput("Texture","Texture"); + this.properties = { flipY: false }; + this.size = [LGraphTexture.image_preview_size, LGraphTexture.image_preview_size]; + } + + LGraphTexturePreview.title = "Preview"; + LGraphTexturePreview.desc = "Show a texture in the graph canvas"; + LGraphTexturePreview.allow_preview = false; + + LGraphTexturePreview.prototype.onDrawBackground = function(ctx) + { + if(this.flags.collapsed) + return; + + if(!ctx.webgl && !LGraphTexturePreview.allow_preview) + return; //not working well + + var tex = this.getInputData(0); + if(!tex) + return; + + var tex_canvas = null; + + if(!tex.handle && ctx.webgl) + tex_canvas = tex; + else + tex_canvas = LGraphTexture.generateLowResTexturePreview(tex); + + //render to graph canvas + ctx.save(); + if(this.properties.flipY) + { + ctx.translate(0,this.size[1]); + ctx.scale(1,-1); + } + ctx.drawImage(tex_canvas,0,0,this.size[0],this.size[1]); + ctx.restore(); + } + + LiteGraph.registerNodeType("texture/preview", LGraphTexturePreview ); + + //************************************** + + function LGraphTextureSave() + { + this.addInput("Texture","Texture"); + this.addOutput("","Texture"); + this.properties = {name:""}; + } + + LGraphTextureSave.title = "Save"; + LGraphTextureSave.desc = "Save a texture in the repository"; + + LGraphTextureSave.prototype.onExecute = function() + { + var tex = this.getInputData(0); + if(!tex) + return; + + if(this.properties.name) + { + //for cases where we want to perform something when storing it + if( LGraphTexture.storeTexture ) + LGraphTexture.storeTexture( this.properties.name, tex ); + else + { + var container = LGraphTexture.getTexturesContainer(); + container[ this.properties.name ] = tex; + } + } + + this.setOutputData(0, tex); + } + + LiteGraph.registerNodeType("texture/save", LGraphTextureSave ); + + //**************************************************** + + function LGraphTextureOperation() + { + this.addInput("Texture","Texture"); + this.addInput("TextureB","Texture"); + this.addInput("value","number"); + this.addOutput("Texture","Texture"); + this.help = "

pixelcode must be vec3

\ +

uvcode must be vec2, is optional

\ +

uv: tex. coords

color: texture

colorB: textureB

time: scene time

value: input value

"; + + this.properties = {value:1, uvcode:"", pixelcode:"color + colorB * value", precision: LGraphTexture.DEFAULT }; + } + + LGraphTextureOperation.widgets_info = { + "uvcode": { widget:"textarea", height: 100 }, + "pixelcode": { widget:"textarea", height: 100 }, + "precision": { widget:"combo", values: LGraphTexture.MODE_VALUES } + }; + + LGraphTextureOperation.title = "Operation"; + LGraphTextureOperation.desc = "Texture shader operation"; + + LGraphTextureOperation.prototype.getExtraMenuOptions = function(graphcanvas) + { + var that = this; + var txt = !that.properties.show ? "Show Texture" : "Hide Texture"; + return [ {content: txt, callback: + function() { + that.properties.show = !that.properties.show; + } + }]; + } + + LGraphTextureOperation.prototype.onDrawBackground = function(ctx) + { + if(this.flags.collapsed || this.size[1] <= 20 || !this.properties.show) + return; + + if(!this._tex) + return; + + //only works if using a webgl renderer + if(this._tex.gl != ctx) + return; + + //render to graph canvas + ctx.save(); + ctx.drawImage(this._tex, 0, 0, this.size[0], this.size[1]); + ctx.restore(); + } + + LGraphTextureOperation.prototype.onExecute = function() + { + var tex = this.getInputData(0); + + if(!this.isOutputConnected(0)) + return; //saves work + + if(this.properties.precision === LGraphTexture.PASS_THROUGH) + { + this.setOutputData(0, tex); + return; + } + + var texB = this.getInputData(1); + + if(!this.properties.uvcode && !this.properties.pixelcode) + return; + + var width = 512; + var height = 512; + if(tex) + { + width = tex.width; + height = tex.height; + } + else if (texB) + { + width = texB.width; + height = texB.height; + } + + var type = LGraphTexture.getTextureType( this.properties.precision, tex ); + + if(!tex && !this._tex ) + 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 ); + + var uvcode = ""; + if(this.properties.uvcode) + { + uvcode = "uv = " + this.properties.uvcode; + if(this.properties.uvcode.indexOf(";") != -1) //there are line breaks, means multiline code + uvcode = this.properties.uvcode; + } + + var pixelcode = ""; + if(this.properties.pixelcode) + { + pixelcode = "result = " + this.properties.pixelcode; + if(this.properties.pixelcode.indexOf(";") != -1) //there are line breaks, means multiline code + pixelcode = this.properties.pixelcode; + } + + var shader = this._shader; + + if(!shader || this._shader_code != (uvcode + "|" + pixelcode) ) + { + try + { + this._shader = new GL.Shader(Shader.SCREEN_VERTEX_SHADER, LGraphTextureOperation.pixel_shader, { UV_CODE: uvcode, PIXEL_CODE: pixelcode }); + this.boxcolor = "#00FF00"; + } + catch (err) + { + console.log("Error compiling shader: ", err); + this.boxcolor = "#FF0000"; + return; + } + this.boxcolor = "#FF0000"; + + this._shader_code = (uvcode + "|" + pixelcode); + shader = this._shader; + } + + if(!shader) + { + this.boxcolor = "red"; + return; + } + else + this.boxcolor = "green"; + + var value = this.getInputData(2); + if(value != null) + this.properties.value = value; + else + value = parseFloat( this.properties.value ); + + var time = this.graph.getTime(); + + this._tex.drawTo(function() { + gl.disable( gl.DEPTH_TEST ); + gl.disable( gl.CULL_FACE ); + gl.disable( gl.BLEND ); + if(tex) tex.bind(0); + if(texB) texB.bind(1); + var mesh = Mesh.getScreenQuad(); + shader.uniforms({u_texture:0, u_textureB:1, value: value, texSize:[width,height], time: time}).draw(mesh); + }); + + this.setOutputData(0, this._tex); + } + + LGraphTextureOperation.pixel_shader = "precision highp float;\n\ + \n\ + uniform sampler2D u_texture;\n\ + uniform sampler2D u_textureB;\n\ + varying vec2 v_coord;\n\ + uniform vec2 texSize;\n\ + uniform float time;\n\ + uniform float value;\n\ + \n\ + void main() {\n\ + vec2 uv = v_coord;\n\ + UV_CODE;\n\ + vec4 color4 = texture2D(u_texture, uv);\n\ + vec3 color = color4.rgb;\n\ + vec4 color4B = texture2D(u_textureB, uv);\n\ + vec3 colorB = color4B.rgb;\n\ + vec3 result = color;\n\ + float alpha = 1.0;\n\ + PIXEL_CODE;\n\ + gl_FragColor = vec4(result, alpha);\n\ + }\n\ + "; + + LiteGraph.registerNodeType("texture/operation", LGraphTextureOperation ); + + //**************************************************** + + function LGraphTextureShader() + { + this.addOutput("out","Texture"); + 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 = { in_texture:0, texSize: vec2.create(), time: 0 }; + } + + LGraphTextureShader.title = "Shader"; + LGraphTextureShader.desc = "Texture shader"; + LGraphTextureShader.widgets_info = { + "code": { type:"code" }, + "precision": { widget:"combo", values: LGraphTexture.MODE_VALUES } + }; + + LGraphTextureShader.prototype.onPropertyChanged = function(name, value) + { + if(name != "code") + return; + + var shader = this.getShader(); + if(!shader) + return; + + //update connections + var uniforms = shader.uniformInfo; + + //remove deprecated slots + if(this.inputs) + { + var already = {}; + for(var i = 0; i < this.inputs.length; ++i) + { + var info = this.getInputInfo(i); + if(!info) + continue; + + if( uniforms[ info.name ] && !already[ info.name ] ) + { + already[ info.name ] = true; + continue; + } + this.removeInput(i); + i--; + } + } + + //update existing ones + for(var i in uniforms) + { + var info = shader.uniformInfo[i]; + if(info.loc === null) + continue; //is an attribute, not a uniform + if(i == "time") //default one + continue; + + var type = "number"; + if( this._shader.samplers[i] ) + type = "texture"; + else + { + switch(info.size) + { + case 1: type = "number"; break; + 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: continue; + } + } + + var slot = this.findInputSlot(i); + if(slot == -1) + { + this.addInput(i,type); + continue; + } + + var input_info = this.getInputInfo(slot); + if(!input_info) + this.addInput(i,type); + else + { + if(input_info.type == type) + continue; + this.removeInput(slot,type); + this.addInput(i,type); + } + } + } + + LGraphTextureShader.prototype.getShader = function() + { + //replug + if(this._shader && this._shader_code == this.properties.code) + return this._shader; + + this._shader_code = this.properties.code; + this._shader = new GL.Shader(Shader.SCREEN_VERTEX_SHADER, LGraphTextureShader.pixel_shader + this.properties.code ); + if(!this._shader) { + this.boxcolor = "red"; + return null; + } + else + this.boxcolor = "green"; + return this._shader; + } + + LGraphTextureShader.prototype.onExecute = function() + { + if(!this.isOutputConnected(0)) + return; //saves work + + var shader = this.getShader(); + if(!shader) + return; + + var tex_slot = 0; + var in_tex = null; + + //set uniforms + for(var i = 0; i < this.inputs.length; ++i) + { + var info = this.getInputInfo(i); + var data = this.getInputData(i); + if(data == null) + continue; + + if(data.constructor === GL.Texture) + { + data.bind(tex_slot); + if(!in_tex) + in_tex = data; + data = tex_slot; + tex_slot++; + } + shader.setUniform( info.name, data ); //data is tex_slot + } + + var uniforms = this._uniforms; + var type = LGraphTexture.getTextureType( this.properties.precision, in_tex ); + + //render to texture + var w = this.properties.width|0; + var h = this.properties.height|0; + if(w == 0) + w = in_tex ? in_tex.width : gl.canvas.width; + if(h == 0) + h = in_tex ? in_tex.height : gl.canvas.height; + uniforms.texSize[0] = w; + uniforms.texSize[1] = h; + uniforms.time = this.graph.getTime(); + + 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; + tex.drawTo(function() { + shader.uniforms( uniforms ).draw( GL.Mesh.getScreenQuad() ); + }); + + this.setOutputData( 0, this._tex ); + } + + LGraphTextureShader.pixel_shader = "precision highp float;\n\ + \n\ + varying vec2 v_coord;\n\ + uniform float time;\n\ + "; + + LiteGraph.registerNodeType("texture/shader", LGraphTextureShader ); + + // Texture Scale Offset + + function LGraphTextureScaleOffset() + { + this.addInput("in","Texture"); + this.addInput("scale","vec2"); + this.addInput("offset","vec2"); + this.addOutput("out","Texture"); + this.properties = { offset: vec2.fromValues(0,0), scale: vec2.fromValues(1,1), precision: LGraphTexture.DEFAULT }; + } + + LGraphTextureScaleOffset.widgets_info = { + "precision": { widget:"combo", values: LGraphTexture.MODE_VALUES } + }; + + LGraphTextureScaleOffset.title = "Scale/Offset"; + LGraphTextureScaleOffset.desc = "Applies an scaling and offseting"; + + LGraphTextureScaleOffset.prototype.onExecute = function() + { + var tex = this.getInputData(0); + + if(!this.isOutputConnected(0) || !tex) + return; //saves work + + if(this.properties.precision === LGraphTexture.PASS_THROUGH) + { + this.setOutputData(0, tex); + return; + } + + var width = tex.width; + var height = tex.height; + var type = this.precision === LGraphTexture.LOW ? gl.UNSIGNED_BYTE : gl.HIGH_PRECISION_FORMAT; + if (this.precision === LGraphTexture.DEFAULT) + type = tex.type; + + 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 shader = this._shader; + + if(!shader) + shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureScaleOffset.pixel_shader ); + + var scale = this.getInputData(1); + if(scale) + { + this.properties.scale[0] = scale[0]; + this.properties.scale[1] = scale[1]; + } + else + scale = this.properties.scale; + + var offset = this.getInputData(2); + if(offset) + { + this.properties.offset[0] = offset[0]; + this.properties.offset[1] = offset[1]; + } + else + offset = this.properties.offset; + + this._tex.drawTo(function() { + gl.disable( gl.DEPTH_TEST ); + gl.disable( gl.CULL_FACE ); + gl.disable( gl.BLEND ); + tex.bind(0); + var mesh = Mesh.getScreenQuad(); + shader.uniforms({u_texture:0, u_scale: scale, u_offset: offset}).draw( mesh ); + }); + + this.setOutputData( 0, this._tex ); + } + + LGraphTextureScaleOffset.pixel_shader = "precision highp float;\n\ + \n\ + uniform sampler2D u_texture;\n\ + uniform sampler2D u_textureB;\n\ + varying vec2 v_coord;\n\ + uniform vec2 u_scale;\n\ + uniform vec2 u_offset;\n\ + \n\ + void main() {\n\ + vec2 uv = v_coord;\n\ + uv = uv / u_scale - u_offset;\n\ + gl_FragColor = texture2D(u_texture, uv);\n\ + }\n\ + "; + + LiteGraph.registerNodeType("texture/scaleOffset", LGraphTextureScaleOffset ); + + + + // Warp (distort a texture) ************************* + + function LGraphTextureWarp() + { + this.addInput("in","Texture"); + this.addInput("warp","Texture"); + this.addInput("factor","number"); + this.addOutput("out","Texture"); + this.properties = { factor: 0.01, precision: LGraphTexture.DEFAULT }; + } + + LGraphTextureWarp.widgets_info = { + "precision": { widget:"combo", values: LGraphTexture.MODE_VALUES } + }; + + LGraphTextureWarp.title = "Warp"; + LGraphTextureWarp.desc = "Texture warp operation"; + + LGraphTextureWarp.prototype.onExecute = function() + { + var tex = this.getInputData(0); + + if(!this.isOutputConnected(0)) + return; //saves work + + if(this.properties.precision === LGraphTexture.PASS_THROUGH) + { + this.setOutputData(0, tex); + return; + } + + var texB = this.getInputData(1); + + 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; + } + + 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 }); + else + this._tex = LGraphTexture.getTargetTexture( tex || this._tex, this._tex, this.properties.precision ); + + var shader = this._shader; + + if(!shader) + shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureWarp.pixel_shader ); + + var factor = this.getInputData(2); + if(factor != null) + this.properties.factor = factor; + else + factor = parseFloat( this.properties.factor ); + + this._tex.drawTo(function() { + gl.disable( gl.DEPTH_TEST ); + gl.disable( gl.CULL_FACE ); + gl.disable( gl.BLEND ); + if(tex) tex.bind(0); + if(texB) texB.bind(1); + var mesh = Mesh.getScreenQuad(); + shader.uniforms({u_texture:0, u_textureB:1, u_factor: factor }).draw( mesh ); + }); + + this.setOutputData(0, this._tex); + } + + LGraphTextureWarp.pixel_shader = "precision highp float;\n\ + \n\ + uniform sampler2D u_texture;\n\ + uniform sampler2D u_textureB;\n\ + varying vec2 v_coord;\n\ + uniform float u_factor;\n\ + \n\ + void main() {\n\ + vec2 uv = v_coord;\n\ + uv += ( texture2D(u_textureB, uv).rg - vec2(0.5)) * u_factor;\n\ + gl_FragColor = texture2D(u_texture, uv);\n\ + }\n\ + "; + + LiteGraph.registerNodeType("texture/warp", LGraphTextureWarp ); + + //**************************************************** + + // Texture to Viewport ***************************************** + function LGraphTextureToViewport() + { + this.addInput("Texture","Texture"); + this.properties = { additive: false, antialiasing: false, filter: true, disable_alpha: false, gamma: 1.0 }; + this.size[0] = 130; + } + + LGraphTextureToViewport.title = "to Viewport"; + LGraphTextureToViewport.desc = "Texture to viewport"; + + LGraphTextureToViewport.prototype.onExecute = function() + { + var tex = this.getInputData(0); + if(!tex) + return; + + if(this.properties.disable_alpha) + gl.disable( gl.BLEND ); + else + { + gl.enable( gl.BLEND ); + if(this.properties.additive) + gl.blendFunc( gl.SRC_ALPHA, gl.ONE ); + else + gl.blendFunc( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA ); + } + + gl.disable( gl.DEPTH_TEST ); + var gamma = this.properties.gamma || 1.0; + if( this.isInputConnected(1) ) + gamma = this.getInputData(1); + + tex.setParameter( gl.TEXTURE_MAG_FILTER, this.properties.filter ? gl.LINEAR : gl.NEAREST ); + + if(this.properties.antialiasing) + { + if(!LGraphTextureToViewport._shader) + LGraphTextureToViewport._shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureToViewport.aa_pixel_shader ); + + var viewport = gl.getViewport(); //gl.getParameter(gl.VIEWPORT); + var mesh = Mesh.getScreenQuad(); + tex.bind(0); + LGraphTextureToViewport._shader.uniforms({u_texture:0, uViewportSize:[tex.width,tex.height], u_igamma: 1 / gamma, inverseVP: [1/tex.width,1/tex.height] }).draw(mesh); + } + else + { + if(gamma != 1.0) + { + if(!LGraphTextureToViewport._gamma_shader) + LGraphTextureToViewport._gamma_shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphTextureToViewport.gamma_pixel_shader ); + tex.toViewport(LGraphTextureToViewport._gamma_shader, { u_texture:0, u_igamma: 1 / gamma }); + } + else + tex.toViewport(); + } + } + + LGraphTextureToViewport.prototype.onGetInputs = function() + { + return [["gamma","number"]]; + } + + LGraphTextureToViewport.aa_pixel_shader = "precision highp float;\n\ + precision highp float;\n\ + varying vec2 v_coord;\n\ + uniform sampler2D u_texture;\n\ + uniform vec2 uViewportSize;\n\ + uniform vec2 inverseVP;\n\ + uniform float u_igamma;\n\ + #define FXAA_REDUCE_MIN (1.0/ 128.0)\n\ + #define FXAA_REDUCE_MUL (1.0 / 8.0)\n\ + #define FXAA_SPAN_MAX 8.0\n\ + \n\ + /* from mitsuhiko/webgl-meincraft based on the code on geeks3d.com */\n\ + vec4 applyFXAA(sampler2D tex, vec2 fragCoord)\n\ + {\n\ + vec4 color = vec4(0.0);\n\ + /*vec2 inverseVP = vec2(1.0 / uViewportSize.x, 1.0 / uViewportSize.y);*/\n\ + vec3 rgbNW = texture2D(tex, (fragCoord + vec2(-1.0, -1.0)) * inverseVP).xyz;\n\ + vec3 rgbNE = texture2D(tex, (fragCoord + vec2(1.0, -1.0)) * inverseVP).xyz;\n\ + vec3 rgbSW = texture2D(tex, (fragCoord + vec2(-1.0, 1.0)) * inverseVP).xyz;\n\ + vec3 rgbSE = texture2D(tex, (fragCoord + vec2(1.0, 1.0)) * inverseVP).xyz;\n\ + vec3 rgbM = texture2D(tex, fragCoord * inverseVP).xyz;\n\ + vec3 luma = vec3(0.299, 0.587, 0.114);\n\ + float lumaNW = dot(rgbNW, luma);\n\ + float lumaNE = dot(rgbNE, luma);\n\ + float lumaSW = dot(rgbSW, luma);\n\ + float lumaSE = dot(rgbSE, luma);\n\ + float lumaM = dot(rgbM, luma);\n\ + float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE)));\n\ + float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE)));\n\ + \n\ + vec2 dir;\n\ + dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE));\n\ + dir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE));\n\ + \n\ + float dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) * (0.25 * FXAA_REDUCE_MUL), FXAA_REDUCE_MIN);\n\ + \n\ + float rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce);\n\ + dir = min(vec2(FXAA_SPAN_MAX, FXAA_SPAN_MAX), max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX), dir * rcpDirMin)) * inverseVP;\n\ + \n\ + vec3 rgbA = 0.5 * (texture2D(tex, fragCoord * inverseVP + dir * (1.0 / 3.0 - 0.5)).xyz + \n\ + texture2D(tex, fragCoord * inverseVP + dir * (2.0 / 3.0 - 0.5)).xyz);\n\ + vec3 rgbB = rgbA * 0.5 + 0.25 * (texture2D(tex, fragCoord * inverseVP + dir * -0.5).xyz + \n\ + texture2D(tex, fragCoord * inverseVP + dir * 0.5).xyz);\n\ + \n\ + //return vec4(rgbA,1.0);\n\ + float lumaB = dot(rgbB, luma);\n\ + if ((lumaB < lumaMin) || (lumaB > lumaMax))\n\ + color = vec4(rgbA, 1.0);\n\ + else\n\ + color = vec4(rgbB, 1.0);\n\ + if(u_igamma != 1.0)\n\ + color.xyz = pow( color.xyz, vec3(u_igamma) );\n\ + return color;\n\ + }\n\ + \n\ + void main() {\n\ + gl_FragColor = applyFXAA( u_texture, v_coord * uViewportSize) ;\n\ + }\n\ + "; + + LGraphTextureToViewport.gamma_pixel_shader = "precision highp float;\n\ + precision highp float;\n\ + varying vec2 v_coord;\n\ + uniform sampler2D u_texture;\n\ + uniform float u_igamma;\n\ + void main() {\n\ + vec4 color = texture2D( u_texture, v_coord);\n\ + color.xyz = pow(color.xyz, vec3(u_igamma) );\n\ + gl_FragColor = color;\n\ + }\n\ + "; + + + LiteGraph.registerNodeType("texture/toviewport", LGraphTextureToViewport ); + + + // Texture Copy ***************************************** + function LGraphTextureCopy() + { + this.addInput("Texture","Texture"); + this.addOutput("","Texture"); + this.properties = { size: 0, generate_mipmaps: false, precision: LGraphTexture.DEFAULT }; + } + + LGraphTextureCopy.title = "Copy"; + LGraphTextureCopy.desc = "Copy Texture"; + LGraphTextureCopy.widgets_info = { + size: { widget:"combo", values:[0,32,64,128,256,512,1024,2048]}, + precision: { widget:"combo", values: LGraphTexture.MODE_VALUES } + }; + + LGraphTextureCopy.prototype.onExecute = function() + { + var tex = this.getInputData(0); + if(!tex && !this._temp_texture) + return; + + if(!this.isOutputConnected(0)) + return; //saves work + + //copy the texture + if(tex) + { + var width = tex.width; + var height = tex.height; + + if(this.properties.size != 0) + { + width = this.properties.size; + height = this.properties.size; + } + + var temp = this._temp_texture; + + var type = tex.type; + if(this.properties.precision === LGraphTexture.LOW) + type = gl.UNSIGNED_BYTE; + else if(this.properties.precision === LGraphTexture.HIGH) + type = gl.HIGH_PRECISION_FORMAT; + + if(!temp || temp.width != width || temp.height != height || temp.type != type ) + { + var minFilter = gl.LINEAR; + if( this.properties.generate_mipmaps && isPowerOfTwo(width) && isPowerOfTwo(height) ) + minFilter = gl.LINEAR_MIPMAP_LINEAR; + this._temp_texture = new GL.Texture( width, height, { type: type, format: gl.RGBA, minFilter: minFilter, magFilter: gl.LINEAR }); + } + tex.copyTo(this._temp_texture); + + if(this.properties.generate_mipmaps) + { + this._temp_texture.bind(0); + gl.generateMipmap(this._temp_texture.texture_type); + this._temp_texture.unbind(0); + } + } + + + this.setOutputData(0,this._temp_texture); + } + + LiteGraph.registerNodeType("texture/copy", LGraphTextureCopy ); + + + // Texture Downsample ***************************************** + function LGraphTextureDownsample() + { + this.addInput("Texture","Texture"); + this.addOutput("","Texture"); + this.properties = { iterations: 1, generate_mipmaps: false, precision: LGraphTexture.DEFAULT }; + } + + LGraphTextureDownsample.title = "Downsample"; + LGraphTextureDownsample.desc = "Downsample Texture"; + LGraphTextureDownsample.widgets_info = { + iterations: { type:"number", step: 1, precision: 0, min: 1 }, + precision: { widget:"combo", values: LGraphTexture.MODE_VALUES } + }; + + LGraphTextureDownsample.prototype.onExecute = function() + { + var tex = this.getInputData(0); + if(!tex && !this._temp_texture) + return; + + if(!this.isOutputConnected(0)) + return; //saves work + + //we do not allow any texture different than texture 2D + if(!tex || tex.texture_type !== GL.TEXTURE_2D ) + return; + + var shader = LGraphTextureDownsample._shader; + if(!shader) + LGraphTextureDownsample._shader = shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureDownsample.pixel_shader ); + + var width = tex.width|0; + var height = tex.height|0; + var type = tex.type; + if(this.properties.precision === LGraphTexture.LOW) + type = gl.UNSIGNED_BYTE; + else if(this.properties.precision === LGraphTexture.HIGH) + type = gl.HIGH_PRECISION_FORMAT; + var iterations = this.properties.iterations || 1; + + var origin = tex; + var target = null; + + var temp = []; + var options = { + type: type, + format: tex.format + }; + + var offset = vec2.create(); + var uniforms = { + u_offset: offset + }; + + if( this._texture ) + GL.Texture.releaseTemporary( this._texture ); + + for(var i = 0; i < iterations; ++i) + { + offset[0] = 1/width; + offset[1] = 1/height; + width = width>>1 || 0; + height = height>>1 || 0; + target = GL.Texture.getTemporary( width, height, options ); + temp.push( target ); + origin.setParameter( GL.TEXTURE_MAG_FILTER, GL.NEAREST ); + origin.copyTo( target, shader, uniforms ); + if(width == 1 && height == 1) + break; //nothing else to do + origin = target; + } + + //keep the last texture used + this._texture = temp.pop(); + + //free the rest + for(var i = 0; i < temp.length; ++i) + GL.Texture.releaseTemporary( temp[i] ); + + if(this.properties.generate_mipmaps) + { + this._texture.bind(0); + gl.generateMipmap(this._texture.texture_type); + this._texture.unbind(0); + } + + this.setOutputData(0,this._texture); + } + + LGraphTextureDownsample.pixel_shader = "precision highp float;\n\ + precision highp float;\n\ + uniform sampler2D u_texture;\n\ + uniform vec2 u_offset;\n\ + varying vec2 v_coord;\n\ + \n\ + void main() {\n\ + vec4 color = texture2D(u_texture, v_coord );\n\ + color += texture2D(u_texture, v_coord + vec2( u_offset.x, 0.0 ) );\n\ + color += texture2D(u_texture, v_coord + vec2( 0.0, u_offset.y ) );\n\ + color += texture2D(u_texture, v_coord + vec2( u_offset.x, u_offset.y ) );\n\ + gl_FragColor = color * 0.25;\n\ + }\n\ + "; + + LiteGraph.registerNodeType("texture/downsample", LGraphTextureDownsample ); + + + + // Texture Copy ***************************************** + function LGraphTextureAverage() + { + this.addInput("Texture","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"; + LGraphTextureAverage.desc = "Compute a partial average (32 random samples) of a texture and stores it as a 1x1 pixel texture"; + + LGraphTextureAverage.prototype.onExecute = function() + { + var tex = this.getInputData(0); + if(!tex) + return; + + if(!this.isOutputConnected(0) && !this.isOutputConnected(1) && !this.isOutputConnected(2)) + return; //saves work + + if(!LGraphTextureAverage._shader) + { + LGraphTextureAverage._shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureAverage.pixel_shader); + //creates 32 random numbers and stores the, in two mat4 + var samples = new Float32Array(32); + for(var i = 0; i < 32; ++i) + samples[i] = Math.random(); + LGraphTextureAverage._shader.uniforms({u_samples_a: samples.subarray(0,16), u_samples_b: samples.subarray(16,32) }); + } + + var temp = this._temp_texture; + 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 }); + + var shader = LGraphTextureAverage._shader; + var uniforms = this._uniforms; + uniforms.u_mipmap_offset = this.properties.mipmap_offset; + this._temp_texture.drawTo(function(){ + tex.toViewport( shader, uniforms ); + }); + + 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\ + precision highp float;\n\ + uniform mat4 u_samples_a;\n\ + uniform mat4 u_samples_b;\n\ + uniform sampler2D u_texture;\n\ + uniform float u_mipmap_offset;\n\ + varying vec2 v_coord;\n\ + \n\ + void main() {\n\ + vec4 color = vec4(0.0);\n\ + for(int i = 0; i < 4; ++i)\n\ + for(int j = 0; j < 4; ++j)\n\ + {\n\ + color += texture2D(u_texture, vec2( u_samples_a[i][j], u_samples_b[i][j] ), u_mipmap_offset );\n\ + color += texture2D(u_texture, vec2( 1.0 - u_samples_a[i][j], 1.0 - u_samples_b[i][j] ), u_mipmap_offset );\n\ + }\n\ + gl_FragColor = color * 0.03125;\n\ + }\n\ + "; + + LiteGraph.registerNodeType("texture/average", LGraphTextureAverage ); + + // Image To Texture ***************************************** + function LGraphImageToTexture() + { + this.addInput("Image","image"); + this.addOutput("","Texture"); + this.properties = {}; + } + + LGraphImageToTexture.title = "Image to Texture"; + LGraphImageToTexture.desc = "Uploads an image to the GPU"; + //LGraphImageToTexture.widgets_info = { size: { widget:"combo", values:[0,32,64,128,256,512,1024,2048]} }; + + LGraphImageToTexture.prototype.onExecute = function() + { + var img = this.getInputData(0); + if(!img) + return; + + var width = img.videoWidth || img.width; + var height = img.videoHeight || img.height; + + //this is in case we are using a webgl canvas already, no need to reupload it + if(img.gltexture) + { + this.setOutputData(0,img.gltexture); + return; + } + + + var temp = this._temp_texture; + if(!temp || temp.width != width || temp.height != height ) + this._temp_texture = new GL.Texture( width, height, { format: gl.RGBA, filter: gl.LINEAR }); + + try + { + this._temp_texture.uploadImage(img); + } + catch(err) + { + console.error("image comes from an unsafe location, cannot be uploaded to webgl: " + err); + return; + } + + this.setOutputData(0,this._temp_texture); + } + + LiteGraph.registerNodeType("texture/imageToTexture", LGraphImageToTexture ); + + + // Texture LUT ***************************************** + function LGraphTextureLUT() + { + this.addInput("Texture","Texture"); + this.addInput("LUT","Texture"); + this.addInput("Intensity","number"); + this.addOutput("","Texture"); + this.properties = { intensity: 1, precision: LGraphTexture.DEFAULT, texture: null }; + + if(!LGraphTextureLUT._shader) + LGraphTextureLUT._shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphTextureLUT.pixel_shader ); + } + + LGraphTextureLUT.widgets_info = { + "texture": { widget:"texture"}, + "precision": { widget:"combo", values: LGraphTexture.MODE_VALUES } + }; + + LGraphTextureLUT.title = "LUT"; + LGraphTextureLUT.desc = "Apply LUT to Texture"; + + LGraphTextureLUT.prototype.onExecute = function() + { + if(!this.isOutputConnected(0)) + return; //saves work + + var tex = this.getInputData(0); + + if(this.properties.precision === LGraphTexture.PASS_THROUGH ) + { + this.setOutputData(0,tex); + return; + } + + if(!tex) + return; + + var lut_tex = this.getInputData(1); + + if(!lut_tex) + lut_tex = LGraphTexture.getTexture( this.properties.texture ); + + if(!lut_tex) + { + this.setOutputData(0,tex); + return; + } + + lut_tex.bind(0); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR ); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE ); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE ); + gl.bindTexture(gl.TEXTURE_2D, null); + + var intensity = this.properties.intensity; + if( this.isInputConnected(2) ) + this.properties.intensity = intensity = this.getInputData(2); + + this._tex = LGraphTexture.getTargetTexture( tex, this._tex, this.properties.precision ); + + //var mesh = Mesh.getScreenQuad(); + + this._tex.drawTo(function() { + lut_tex.bind(1); + tex.toViewport( LGraphTextureLUT._shader, {u_texture:0, u_textureB:1, u_amount: intensity} ); + }); + + this.setOutputData(0,this._tex); + } + + LGraphTextureLUT.pixel_shader = "precision highp float;\n\ + precision highp float;\n\ + varying vec2 v_coord;\n\ + uniform sampler2D u_texture;\n\ + uniform sampler2D u_textureB;\n\ + uniform float u_amount;\n\ + \n\ + void main() {\n\ + lowp vec4 textureColor = clamp( texture2D(u_texture, v_coord), vec4(0.0), vec4(1.0) );\n\ + mediump float blueColor = textureColor.b * 63.0;\n\ + mediump vec2 quad1;\n\ + quad1.y = floor(floor(blueColor) / 8.0);\n\ + quad1.x = floor(blueColor) - (quad1.y * 8.0);\n\ + mediump vec2 quad2;\n\ + quad2.y = floor(ceil(blueColor) / 8.0);\n\ + quad2.x = ceil(blueColor) - (quad2.y * 8.0);\n\ + highp vec2 texPos1;\n\ + texPos1.x = (quad1.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r);\n\ + texPos1.y = 1.0 - ((quad1.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.g));\n\ + highp vec2 texPos2;\n\ + texPos2.x = (quad2.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r);\n\ + texPos2.y = 1.0 - ((quad2.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.g));\n\ + lowp vec4 newColor1 = texture2D(u_textureB, texPos1);\n\ + lowp vec4 newColor2 = texture2D(u_textureB, texPos2);\n\ + lowp vec4 newColor = mix(newColor1, newColor2, fract(blueColor));\n\ + gl_FragColor = vec4( mix( textureColor.rgb, newColor.rgb, u_amount), textureColor.w);\n\ + }\n\ + "; + + LiteGraph.registerNodeType("texture/LUT", LGraphTextureLUT ); + + // Texture Channels ***************************************** + function LGraphTextureChannels() + { + this.addInput("Texture","Texture"); + + this.addOutput("R","Texture"); + this.addOutput("G","Texture"); + this.addOutput("B","Texture"); + this.addOutput("A","Texture"); + + this.properties = {}; + if(!LGraphTextureChannels._shader) + LGraphTextureChannels._shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphTextureChannels.pixel_shader ); + } + + LGraphTextureChannels.title = "Texture to Channels"; + LGraphTextureChannels.desc = "Split texture channels"; + + LGraphTextureChannels.prototype.onExecute = function() + { + var texA = this.getInputData(0); + if(!texA) return; + + if(!this._channels) + this._channels = Array(4); + + var connections = 0; + for(var i = 0; i < 4; i++) + { + if(this.isOutputConnected(i)) + { + if(!this._channels[i] || this._channels[i].width != texA.width || this._channels[i].height != texA.height || this._channels[i].type != texA.type) + this._channels[i] = new GL.Texture( texA.width, texA.height, { type: texA.type, format: gl.RGBA, filter: gl.LINEAR }); + connections++; + } + else + this._channels[i] = null; + } + + if(!connections) + return; + + gl.disable( gl.BLEND ); + gl.disable( gl.DEPTH_TEST ); + + var mesh = Mesh.getScreenQuad(); + var shader = LGraphTextureChannels._shader; + var masks = [[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]]; + + for(var i = 0; i < 4; i++) + { + if(!this._channels[i]) + continue; + + this._channels[i].drawTo( function() { + texA.bind(0); + shader.uniforms({u_texture:0, u_mask: masks[i]}).draw(mesh); + }); + this.setOutputData(i, this._channels[i]); + } + } + + LGraphTextureChannels.pixel_shader = "precision highp float;\n\ + precision highp float;\n\ + varying vec2 v_coord;\n\ + uniform sampler2D u_texture;\n\ + uniform vec4 u_mask;\n\ + \n\ + void main() {\n\ + gl_FragColor = vec4( vec3( length( texture2D(u_texture, v_coord) * u_mask )), 1.0 );\n\ + }\n\ + "; + + LiteGraph.registerNodeType("texture/textureChannels", LGraphTextureChannels ); + + + // Texture Channels to Texture ***************************************** + function LGraphChannelsTexture() + { + this.addInput("R","Texture"); + this.addInput("G","Texture"); + this.addInput("B","Texture"); + this.addInput("A","Texture"); + + this.addOutput("Texture","Texture"); + + this.properties = {}; + if(!LGraphChannelsTexture._shader) + LGraphChannelsTexture._shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphChannelsTexture.pixel_shader ); + } + + LGraphChannelsTexture.title = "Channels to Texture"; + LGraphChannelsTexture.desc = "Split texture channels"; + + LGraphChannelsTexture.prototype.onExecute = function() + { + var tex = [ this.getInputData(0), + this.getInputData(1), + this.getInputData(2), + this.getInputData(3) ]; + + if(!tex[0] || !tex[1] || !tex[2] || !tex[3]) + return; + + gl.disable( gl.BLEND ); + gl.disable( gl.DEPTH_TEST ); + + var mesh = Mesh.getScreenQuad(); + var shader = LGraphChannelsTexture._shader; + + this._tex = LGraphTexture.getTargetTexture( tex[0], this._tex ); + + this._tex.drawTo( function() { + tex[0].bind(0); + tex[1].bind(1); + tex[2].bind(2); + tex[3].bind(3); + shader.uniforms({u_textureR:0, u_textureG:1, u_textureB:2, u_textureA:3 }).draw(mesh); + }); + this.setOutputData(0, this._tex); + } + + LGraphChannelsTexture.pixel_shader = "precision highp float;\n\ + precision highp float;\n\ + varying vec2 v_coord;\n\ + uniform sampler2D u_textureR;\n\ + uniform sampler2D u_textureG;\n\ + uniform sampler2D u_textureB;\n\ + uniform sampler2D u_textureA;\n\ + \n\ + void main() {\n\ + gl_FragColor = vec4( \ + texture2D(u_textureR, v_coord).r,\ + texture2D(u_textureG, v_coord).r,\ + texture2D(u_textureB, v_coord).r,\ + texture2D(u_textureA, v_coord).r);\n\ + }\n\ + "; + + LiteGraph.registerNodeType("texture/channelsTexture", LGraphChannelsTexture ); + + // Texture Channels to Texture ***************************************** + function LGraphTextureGradient() + { + this.addInput("A","color"); + this.addInput("B","color"); + this.addOutput("Texture","Texture"); + + this.properties = { angle: 0, scale: 1, A:[0,0,0], B:[1,1,1], texture_size:32 }; + if(!LGraphTextureGradient._shader) + LGraphTextureGradient._shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphTextureGradient.pixel_shader ); + + this._uniforms = { u_angle: 0, u_colorA: vec3.create(), u_colorB: vec3.create()}; + } + + LGraphTextureGradient.title = "Gradient"; + LGraphTextureGradient.desc = "Generates a gradient"; + LGraphTextureGradient["@A"] = { type:"color" }; + LGraphTextureGradient["@B"] = { type:"color" }; + LGraphTextureGradient["@texture_size"] = { type:"enum", values:[32,64,128,256,512] }; + + LGraphTextureGradient.prototype.onExecute = function() + { + gl.disable( gl.BLEND ); + gl.disable( gl.DEPTH_TEST ); + + var mesh = GL.Mesh.getScreenQuad(); + var shader = LGraphTextureGradient._shader; + + var A = this.getInputData(0); + if(!A) + A = this.properties.A; + var B = this.getInputData(1); + if(!B) + B = this.properties.B; + + //angle and scale + for(var i = 2; i < this.inputs.length; i++) + { + var input = this.inputs[i]; + var v = this.getInputData(i); + if(v === undefined) + continue; + this.properties[ input.name ] = v; + } + + var uniforms = this._uniforms; + this._uniforms.u_angle = this.properties.angle * DEG2RAD; + this._uniforms.u_scale = this.properties.scale; + vec3.copy( uniforms.u_colorA, A ); + vec3.copy( uniforms.u_colorB, B ); + + var size = parseInt( this.properties.texture_size ); + if(!this._tex || this._tex.width != size ) + this._tex = new GL.Texture( size, size, { format: gl.RGB, filter: gl.LINEAR }); + + this._tex.drawTo( function() { + shader.uniforms(uniforms).draw(mesh); + }); + this.setOutputData(0, this._tex); + } + + LGraphTextureGradient.prototype.onGetInputs = function() + { + return [["angle","number"],["scale","number"]]; + } + + LGraphTextureGradient.pixel_shader = "precision highp float;\n\ + precision highp float;\n\ + varying vec2 v_coord;\n\ + uniform float u_angle;\n\ + uniform float u_scale;\n\ + uniform vec3 u_colorA;\n\ + uniform vec3 u_colorB;\n\ + \n\ + vec2 rotate(vec2 v, float angle)\n\ + {\n\ + vec2 result;\n\ + float _cos = cos(angle);\n\ + float _sin = sin(angle);\n\ + result.x = v.x * _cos - v.y * _sin;\n\ + result.y = v.x * _sin + v.y * _cos;\n\ + return result;\n\ + }\n\ + void main() {\n\ + float f = (rotate(u_scale * (v_coord - vec2(0.5)), u_angle) + vec2(0.5)).x;\n\ + vec3 color = mix(u_colorA,u_colorB,clamp(f,0.0,1.0));\n\ + gl_FragColor = vec4(color,1.0);\n\ + }\n\ + "; + + LiteGraph.registerNodeType("texture/gradient", LGraphTextureGradient ); + + // Texture Mix ***************************************** + function LGraphTextureMix() + { + this.addInput("A","Texture"); + this.addInput("B","Texture"); + this.addInput("Mixer","Texture"); + + this.addOutput("Texture","Texture"); + this.properties = { precision: LGraphTexture.DEFAULT }; + + if(!LGraphTextureMix._shader) + LGraphTextureMix._shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphTextureMix.pixel_shader ); + } + + LGraphTextureMix.title = "Mix"; + LGraphTextureMix.desc = "Generates a texture mixing two textures"; + + LGraphTextureMix.widgets_info = { + "precision": { widget:"combo", values: LGraphTexture.MODE_VALUES } + }; + + LGraphTextureMix.prototype.onExecute = function() + { + var texA = this.getInputData(0); + + if(!this.isOutputConnected(0)) + return; //saves work + + if(this.properties.precision === LGraphTexture.PASS_THROUGH ) + { + this.setOutputData(0,texA); + return; + } + + var texB = this.getInputData(1); + var texMix = this.getInputData(2); + if(!texA || !texB || !texMix) return; + + this._tex = LGraphTexture.getTargetTexture( texA, this._tex, this.properties.precision ); + + gl.disable( gl.BLEND ); + gl.disable( gl.DEPTH_TEST ); + + var mesh = Mesh.getScreenQuad(); + var shader = LGraphTextureMix._shader; + + this._tex.drawTo( function() { + texA.bind(0); + texB.bind(1); + texMix.bind(2); + shader.uniforms({u_textureA:0,u_textureB:1,u_textureMix:2}).draw(mesh); + }); + + this.setOutputData(0, this._tex); + } + + LGraphTextureMix.pixel_shader = "precision highp float;\n\ + precision highp float;\n\ + varying vec2 v_coord;\n\ + uniform sampler2D u_textureA;\n\ + uniform sampler2D u_textureB;\n\ + uniform sampler2D u_textureMix;\n\ + \n\ + void main() {\n\ + gl_FragColor = mix( texture2D(u_textureA, v_coord), texture2D(u_textureB, v_coord), texture2D(u_textureMix, v_coord) );\n\ + }\n\ + "; + + LiteGraph.registerNodeType("texture/mix", LGraphTextureMix ); + + // Texture Edges detection ***************************************** + function LGraphTextureEdges() + { + this.addInput("Tex.","Texture"); + + this.addOutput("Edges","Texture"); + 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 ); + } + + LGraphTextureEdges.title = "Edges"; + LGraphTextureEdges.desc = "Detects edges"; + + LGraphTextureEdges.widgets_info = { + "precision": { widget:"combo", values: LGraphTexture.MODE_VALUES } + }; + + LGraphTextureEdges.prototype.onExecute = function() + { + if(!this.isOutputConnected(0)) + return; //saves work + + var tex = this.getInputData(0); + + if(this.properties.precision === LGraphTexture.PASS_THROUGH ) + { + this.setOutputData(0,tex); + return; + } + + if(!tex) return; + + this._tex = LGraphTexture.getTargetTexture( tex, this._tex, this.properties.precision ); + + gl.disable( gl.BLEND ); + gl.disable( gl.DEPTH_TEST ); + + var mesh = Mesh.getScreenQuad(); + 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_threshold: threshold, u_invert: invert ? 1 : 0}).draw(mesh); + }); + + this.setOutputData(0, this._tex); + } + + LGraphTextureEdges.pixel_shader = "precision highp float;\n\ + precision highp float;\n\ + varying vec2 v_coord;\n\ + uniform sampler2D u_texture;\n\ + 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\ + vec4 up = texture2D(u_texture, v_coord + u_isize * vec2(0.0,1.0) );\n\ + vec4 down = texture2D(u_texture, v_coord + u_isize * vec2(0.0,-1.0) );\n\ + vec4 left = texture2D(u_texture, v_coord + u_isize * vec2(1.0,0.0) );\n\ + vec4 right = texture2D(u_texture, v_coord + u_isize * vec2(-1.0,0.0) );\n\ + vec4 diff = abs(center - up) + abs(center - down) + abs(center - left) + abs(center - right);\n\ + diff *= u_factor;\n\ + if(u_invert == 1)\n\ + diff.xyz = vec3(1.0) - diff.xyz;\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\ + "; + + LiteGraph.registerNodeType("texture/edges", LGraphTextureEdges ); + + // Texture Depth ***************************************** + function LGraphTextureDepthRange() + { + this.addInput("Texture","Texture"); + this.addInput("Distance","number"); + this.addInput("Range","number"); + this.addOutput("Texture","Texture"); + this.properties = { distance:100, range: 50, only_depth: false, high_precision: false }; + this._uniforms = {u_texture:0, u_distance: 100, u_range: 50, u_camera_planes: null }; + } + + LGraphTextureDepthRange.title = "Depth Range"; + LGraphTextureDepthRange.desc = "Generates a texture with a depth range"; + + LGraphTextureDepthRange.prototype.onExecute = function() + { + if(!this.isOutputConnected(0)) + return; //saves work + + var tex = this.getInputData(0); + if(!tex) return; + + var precision = gl.UNSIGNED_BYTE; + if(this.properties.high_precision) + precision = gl.half_float_ext ? gl.HALF_FLOAT_OES : gl.FLOAT; + + if(!this._temp_texture || this._temp_texture.type != precision || + this._temp_texture.width != tex.width || this._temp_texture.height != tex.height) + this._temp_texture = new GL.Texture( tex.width, tex.height, { type: precision, format: gl.RGBA, filter: gl.LINEAR }); + + var uniforms = this._uniforms; + + //iterations + var distance = this.properties.distance; + if( this.isInputConnected(1) ) + { + distance = this.getInputData(1); + this.properties.distance = distance; + } + + var range = this.properties.range; + if( this.isInputConnected(2) ) + { + range = this.getInputData(2); + this.properties.range = range; + } + + uniforms.u_distance = distance; + uniforms.u_range = range; + + gl.disable( gl.BLEND ); + gl.disable( gl.DEPTH_TEST ); + var mesh = Mesh.getScreenQuad(); + if(!LGraphTextureDepthRange._shader) + { + LGraphTextureDepthRange._shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphTextureDepthRange.pixel_shader ); + LGraphTextureDepthRange._shader_onlydepth = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphTextureDepthRange.pixel_shader, { ONLY_DEPTH:""} ); + } + var shader = this.properties.only_depth ? LGraphTextureDepthRange._shader_onlydepth : LGraphTextureDepthRange._shader; + + //NEAR AND FAR PLANES + var planes = null; + if( tex.near_far_planes ) + planes = tex.near_far_planes; + else if( window.LS && LS.Renderer._main_camera ) + planes = LS.Renderer._main_camera._uniforms.u_camera_planes; + else + planes = [0.1,1000]; //hardcoded + uniforms.u_camera_planes = planes; + + + this._temp_texture.drawTo( function() { + tex.bind(0); + shader.uniforms( uniforms ).draw(mesh); + }); + + this._temp_texture.near_far_planes = planes; + this.setOutputData(0, this._temp_texture ); + } + + LGraphTextureDepthRange.pixel_shader = "precision highp float;\n\ + precision highp float;\n\ + varying vec2 v_coord;\n\ + uniform sampler2D u_texture;\n\ + uniform vec2 u_camera_planes;\n\ + uniform float u_distance;\n\ + uniform float u_range;\n\ + \n\ + float LinearDepth()\n\ + {\n\ + float zNear = u_camera_planes.x;\n\ + float zFar = u_camera_planes.y;\n\ + float depth = texture2D(u_texture, v_coord).x;\n\ + depth = depth * 2.0 - 1.0;\n\ + return zNear * (depth + 1.0) / (zFar + zNear - depth * (zFar - zNear));\n\ + }\n\ + \n\ + void main() {\n\ + float depth = LinearDepth();\n\ + #ifdef ONLY_DEPTH\n\ + gl_FragColor = vec4(depth);\n\ + #else\n\ + float diff = abs(depth * u_camera_planes.y - u_distance);\n\ + float dof = 1.0;\n\ + if(diff <= u_range)\n\ + dof = diff / u_range;\n\ + gl_FragColor = vec4(dof);\n\ + #endif\n\ + }\n\ + "; + + LiteGraph.registerNodeType("texture/depth_range", LGraphTextureDepthRange ); + + // Texture Blur ***************************************** + function LGraphTextureBlur() + { + this.addInput("Texture","Texture"); + this.addInput("Iterations","number"); + this.addInput("Intensity","number"); + this.addOutput("Blurred","Texture"); + 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() + { + var tex = this.getInputData(0); + if(!tex) + return; + + if(!this.isOutputConnected(0)) + return; //saves work + + 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 }); + temp = this._final_texture = new GL.Texture( tex.width, tex.height, { type: tex.type, format: gl.RGBA, filter: gl.LINEAR }); + } + + //iterations + var iterations = this.properties.iterations; + if( this.isInputConnected(1) ) + { + iterations = this.getInputData(1); + this.properties.iterations = iterations; + } + iterations = Math.min( Math.floor(iterations), LGraphTextureBlur.max_iterations ); + if(iterations == 0) //skip blurring + { + this.setOutputData(0, tex); + return; + } + + var intensity = this.properties.intensity; + if( this.isInputConnected(2) ) + { + intensity = this.getInputData(2); + this.properties.intensity = intensity; + } + + //blur sometimes needs an aspect correction + var aspect = LiteGraph.camera_aspect; + if(!aspect && window.gl !== undefined) + aspect = gl.canvas.height / gl.canvas.width; + if(!aspect) + aspect = 1; + aspect = this.properties.preserve_aspect ? aspect : 1; + + var scale = this.properties.scale || [1,1]; + 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, temp ); + } + + /* + LGraphTextureBlur.pixel_shader = "precision highp float;\n\ + precision highp float;\n\ + varying vec2 v_coord;\n\ + uniform sampler2D u_texture;\n\ + uniform vec2 u_offset;\n\ + uniform float u_intensity;\n\ + void main() {\n\ + vec4 sum = vec4(0.0);\n\ + vec4 center = texture2D(u_texture, v_coord);\n\ + sum += texture2D(u_texture, v_coord + u_offset * -4.0) * 0.05/0.98;\n\ + sum += texture2D(u_texture, v_coord + u_offset * -3.0) * 0.09/0.98;\n\ + 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\ + sum += center * 0.16/0.98;\n\ + sum += texture2D(u_texture, v_coord + u_offset * 4.0) * 0.05/0.98;\n\ + sum += texture2D(u_texture, v_coord + u_offset * 3.0) * 0.09/0.98;\n\ + 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\ + }\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() + { + this.addInput("Texture","Texture"); + this.addOutput("Filtered","Texture"); + this.properties = { intensity: 1, radius: 5 }; + } + + LGraphTextureKuwaharaFilter.title = "Kuwahara Filter"; + LGraphTextureKuwaharaFilter.desc = "Filters a texture giving an artistic oil canvas painting"; + + LGraphTextureKuwaharaFilter.max_radius = 10; + LGraphTextureKuwaharaFilter._shaders = []; + + LGraphTextureKuwaharaFilter.prototype.onExecute = function() + { + var tex = this.getInputData(0); + if(!tex) + return; + + if(!this.isOutputConnected(0)) + return; //saves work + + var temp = this._temp_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 }); + } + + //iterations + var radius = this.properties.radius; + radius = Math.min( Math.floor(radius), LGraphTextureKuwaharaFilter.max_radius ); + if(radius == 0) //skip blurring + { + this.setOutputData(0, tex); + return; + } + + var intensity = this.properties.intensity; + + //blur sometimes needs an aspect correction + var aspect = LiteGraph.camera_aspect; + if(!aspect && window.gl !== undefined) + aspect = gl.canvas.height / gl.canvas.width; + if(!aspect) + aspect = 1; + aspect = this.properties.preserve_aspect ? aspect : 1; + + if(!LGraphTextureKuwaharaFilter._shaders[ radius ]) + LGraphTextureKuwaharaFilter._shaders[ radius ] = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphTextureKuwaharaFilter.pixel_shader, { RADIUS: radius.toFixed(0) }); + + var shader = LGraphTextureKuwaharaFilter._shaders[ radius ]; + var mesh = GL.Mesh.getScreenQuad(); + tex.bind(0); + + this._temp_texture.drawTo( function() { + shader.uniforms({ u_texture: 0, u_intensity: intensity, u_resolution: [tex.width, tex.height], u_iResolution: [1/tex.width,1/tex.height]}).draw(mesh); + }); + + this.setOutputData(0, this._temp_texture); + } + +//from https://www.shadertoy.com/view/MsXSz4 +LGraphTextureKuwaharaFilter.pixel_shader = "\n\ + precision highp float;\n\ + varying vec2 v_coord;\n\ + uniform sampler2D u_texture;\n\ + uniform float u_intensity;\n\ + uniform vec2 u_resolution;\n\ + uniform vec2 u_iResolution;\n\ + #ifndef RADIUS\n\ + #define RADIUS 7\n\ + #endif\n\ + void main() {\n\ + \n\ + const int radius = RADIUS;\n\ + vec2 fragCoord = v_coord;\n\ + vec2 src_size = u_iResolution;\n\ + vec2 uv = v_coord;\n\ + float n = float((radius + 1) * (radius + 1));\n\ + int i;\n\ + int j;\n\ + vec3 m0 = vec3(0.0); vec3 m1 = vec3(0.0); vec3 m2 = vec3(0.0); vec3 m3 = vec3(0.0);\n\ + vec3 s0 = vec3(0.0); vec3 s1 = vec3(0.0); vec3 s2 = vec3(0.0); vec3 s3 = vec3(0.0);\n\ + vec3 c;\n\ + \n\ + for (int j = -radius; j <= 0; ++j) {\n\ + for (int i = -radius; i <= 0; ++i) {\n\ + c = texture2D(u_texture, uv + vec2(i,j) * src_size).rgb;\n\ + m0 += c;\n\ + s0 += c * c;\n\ + }\n\ + }\n\ + \n\ + for (int j = -radius; j <= 0; ++j) {\n\ + for (int i = 0; i <= radius; ++i) {\n\ + c = texture2D(u_texture, uv + vec2(i,j) * src_size).rgb;\n\ + m1 += c;\n\ + s1 += c * c;\n\ + }\n\ + }\n\ + \n\ + for (int j = 0; j <= radius; ++j) {\n\ + for (int i = 0; i <= radius; ++i) {\n\ + c = texture2D(u_texture, uv + vec2(i,j) * src_size).rgb;\n\ + m2 += c;\n\ + s2 += c * c;\n\ + }\n\ + }\n\ + \n\ + for (int j = 0; j <= radius; ++j) {\n\ + for (int i = -radius; i <= 0; ++i) {\n\ + c = texture2D(u_texture, uv + vec2(i,j) * src_size).rgb;\n\ + m3 += c;\n\ + s3 += c * c;\n\ + }\n\ + }\n\ + \n\ + float min_sigma2 = 1e+2;\n\ + m0 /= n;\n\ + s0 = abs(s0 / n - m0 * m0);\n\ + \n\ + float sigma2 = s0.r + s0.g + s0.b;\n\ + if (sigma2 < min_sigma2) {\n\ + min_sigma2 = sigma2;\n\ + gl_FragColor = vec4(m0, 1.0);\n\ + }\n\ + \n\ + m1 /= n;\n\ + s1 = abs(s1 / n - m1 * m1);\n\ + \n\ + sigma2 = s1.r + s1.g + s1.b;\n\ + if (sigma2 < min_sigma2) {\n\ + min_sigma2 = sigma2;\n\ + gl_FragColor = vec4(m1, 1.0);\n\ + }\n\ + \n\ + m2 /= n;\n\ + s2 = abs(s2 / n - m2 * m2);\n\ + \n\ + sigma2 = s2.r + s2.g + s2.b;\n\ + if (sigma2 < min_sigma2) {\n\ + min_sigma2 = sigma2;\n\ + gl_FragColor = vec4(m2, 1.0);\n\ + }\n\ + \n\ + m3 /= n;\n\ + s3 = abs(s3 / n - m3 * m3);\n\ + \n\ + sigma2 = s3.r + s3.g + s3.b;\n\ + if (sigma2 < min_sigma2) {\n\ + min_sigma2 = sigma2;\n\ + gl_FragColor = vec4(m3, 1.0);\n\ + }\n\ + }\n\ + "; + + LiteGraph.registerNodeType("texture/kuwahara", LGraphTextureKuwaharaFilter ); + + + // Texture Webcam ***************************************** + function LGraphTextureWebcam() + { + this.addOutput("Webcam","Texture"); + this.properties = { texture_name: "" }; + } + + LGraphTextureWebcam.title = "Webcam"; + LGraphTextureWebcam.desc = "Webcam texture"; + + + LGraphTextureWebcam.prototype.openStream = function() + { + //Vendor prefixes hell + navigator.getUserMedia = (navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia); + window.URL = window.URL || window.webkitURL; + + if (!navigator.getUserMedia) { + //console.log('getUserMedia() is not supported in your browser, use chrome and enable WebRTC from about://flags'); + return; + } + + this._waiting_confirmation = true; + var that = this; + + // Not showing vendor prefixes. + navigator.getUserMedia({video: true}, this.streamReady.bind(this), onFailSoHard); + + function onFailSoHard(e) { + console.log('Webcam rejected', e); + that._webcam_stream = false; + that.box_color = "red"; + }; + } + + LGraphTextureWebcam.prototype.streamReady = function(localMediaStream) + { + this._webcam_stream = localMediaStream; + //this._waiting_confirmation = false; + + var video = this._video; + if(!video) + { + video = document.createElement("video"); + video.autoplay = true; + video.src = window.URL.createObjectURL( localMediaStream ); + this._video = video; + //document.body.appendChild( video ); //debug + //when video info is loaded (size and so) + video.onloadedmetadata = function(e) { + // Ready to go. Do some stuff. + console.log(e); + }; + } + } + + LGraphTextureWebcam.prototype.onRemoved = function() + { + if(!this._webcam_stream) + return; + + var video_streams = this._webcam_stream.getVideoTracks(); + if(video_streams.length) + { + var webcam = video_streams[0]; + if( webcam.stop ) + webcam.stop(); + } + + this._webcam_stream = null; + this._video = null; + } + + LGraphTextureWebcam.prototype.onDrawBackground = function(ctx) + { + if(this.flags.collapsed || this.size[1] <= 20) + return; + + if(!this._video) + return; + + //render to graph canvas + ctx.save(); + if(!ctx.webgl) //reverse image + { + ctx.translate(0,this.size[1]); + ctx.scale(1,-1); + ctx.drawImage(this._video, 0, 0, this.size[0], this.size[1]); + } + else + { + if(this._temp_texture) + ctx.drawImage(this._temp_texture, 0, 0, this.size[0], this.size[1]); + } + ctx.restore(); + } + + LGraphTextureWebcam.prototype.onExecute = function() + { + if(this._webcam_stream == null && !this._waiting_confirmation) + this.openStream(); + + if(!this._video || !this._video.videoWidth) + return; + + var width = this._video.videoWidth; + var height = this._video.videoHeight; + + var temp = this._temp_texture; + if(!temp || temp.width != width || temp.height != height ) + this._temp_texture = new GL.Texture( width, height, { format: gl.RGB, filter: gl.LINEAR }); + + this._temp_texture.uploadImage( this._video ); + + if(this.properties.texture_name) + { + var container = LGraphTexture.getTexturesContainer(); + container[ this.properties.texture_name ] = this._temp_texture; + } + + this.setOutputData(0,this._temp_texture); + } + + 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; i= 0xF0) + this.cmd = midiStatus; + else + this.cmd = midiCommand; + + if(this.cmd == MIDIEvent.NOTEON && this.velocity == 0) + this.cmd = MIDIEvent.NOTEOFF; + + this.cmd_str = MIDIEvent.commands[ this.cmd ] || ""; + + if ( midiCommand >= MIDIEvent.NOTEON || midiCommand <= MIDIEvent.NOTEOFF ) { + this.channel = midiStatus & 0x0F; + } +} + +Object.defineProperty( MIDIEvent.prototype, "velocity", { + get: function() { + if(this.cmd == MIDIEvent.NOTEON) + return this.data[2]; + return -1; + }, + set: function(v) { + this.data[2] = v; // v / 127; + }, + enumerable: true +}); + +MIDIEvent.notes = ["A","A#","B","C","C#","D","D#","E","F","F#","G","G#"]; + +//returns HZs +MIDIEvent.prototype.getPitch = function() +{ + return Math.pow(2, (this.data[1] - 69) / 12 ) * 440; +} + +MIDIEvent.computePitch = function( note ) +{ + return Math.pow(2, (note - 69) / 12 ) * 440; +} + +MIDIEvent.prototype.getCC = function() +{ + return this.data[1]; +} + +MIDIEvent.prototype.getCCValue = function() +{ + return this.data[2]; +} + +//not tested, there is a formula missing here +MIDIEvent.prototype.getPitchBend = function() +{ + return this.data[1] + (this.data[2] << 7) - 8192; +} + +MIDIEvent.computePitchBend = function(v1,v2) +{ + return v1 + (v2 << 7) - 8192; +} + +MIDIEvent.prototype.setCommandFromString = function( str ) +{ + this.cmd = MIDIEvent.computeCommandFromString(str); +} + +MIDIEvent.computeCommandFromString = function( str ) +{ + if(!str) + return 0; + + if(str && str.constructor === Number) + return str; + + str = str.toUpperCase(); + switch( str ) + { + case "NOTE ON": + case "NOTEON": return MIDIEvent.NOTEON; break; + case "NOTE OFF": + case "NOTEOFF": return MIDIEvent.NOTEON; break; + case "KEY PRESSURE": + case "KEYPRESSURE": return MIDIEvent.KEYPRESSURE; break; + case "CONTROLLER CHANGE": + case "CONTROLLERCHANGE": + case "CC": return MIDIEvent.CONTROLLERCHANGE; break; + case "PROGRAM CHANGE": + case "PROGRAMCHANGE": + case "PC": return MIDIEvent.PROGRAMCHANGE; break; + case "CHANNEL PRESSURE": + case "CHANNELPRESSURE": return MIDIEvent.CHANNELPRESSURE; break; + case "PITCH BEND": + case "PITCHBEND": return MIDIEvent.PITCHBEND; break; + case "TIME TICK": + case "TIMETICK": return MIDIEvent.TIMETICK; break; + default: return Number(str); //asume its a hex code + } +} + +MIDIEvent.toNoteString = function(d) +{ + var note = d - 21; + var octave = d - 24; + note = note % 12; + if(note < 0) + note = 12 + note; + return MIDIEvent.notes[ note ] + Math.floor(octave / 12 + 1); +} + +MIDIEvent.prototype.toString = function() +{ + var str = "" + this.channel + ". " ; + switch( this.cmd ) + { + case MIDIEvent.NOTEON: str += "NOTEON " + MIDIEvent.toNoteString( this.data[1] ); break; + case MIDIEvent.NOTEOFF: str += "NOTEOFF " + MIDIEvent.toNoteString( this.data[1] ); break; + case MIDIEvent.CONTROLLERCHANGE: str += "CC " + this.data[1] + " " + this.data[2]; break; + case MIDIEvent.PROGRAMCHANGE: str += "PC " + this.data[1]; break; + case MIDIEvent.PITCHBEND: str += "PITCHBEND " + this.getPitchBend(); break; + case MIDIEvent.KEYPRESSURE: str += "KEYPRESS " + this.data[1]; break; + } + + return str; +} + +MIDIEvent.prototype.toHexString = function() +{ + var str = ""; + for(var i = 0; i < this.data.length; i++) + str += this.data[i].toString(16) + " "; +} + +MIDIEvent.NOTEOFF = 0x80; +MIDIEvent.NOTEON = 0x90; +MIDIEvent.KEYPRESSURE = 0xA0; +MIDIEvent.CONTROLLERCHANGE = 0xB0; +MIDIEvent.PROGRAMCHANGE = 0xC0; +MIDIEvent.CHANNELPRESSURE = 0xD0; +MIDIEvent.PITCHBEND = 0xE0; +MIDIEvent.TIMETICK = 0xF8; + +MIDIEvent.commands = { + 0x80: "note off", + 0x90: "note on", + 0xA0: "key pressure", + 0xB0: "controller change", + 0xC0: "program change", + 0xD0: "channel pressure", + 0xE0: "pitch bend", + 0xF0: "system", + 0xF2: "Song pos", + 0xF3: "Song select", + 0xF6: "Tune request", + 0xF8: "time tick", + 0xFA: "Start Song", + 0xFB: "Continue Song", + 0xFC: "Stop Song", + 0xFE: "Sensing", + 0xFF: "Reset" +} + +//MIDI wrapper +function MIDIInterface( on_ready, on_error ) +{ + if(!navigator.requestMIDIAccess) + { + this.error = "not suppoorted"; + if(on_error) + on_error("Not supported"); + else + console.error("MIDI NOT SUPPORTED, enable by chrome://flags"); + return; + } + + this.on_ready = on_ready; + + this.state = { + note: [], + cc: [] + }; + + + + navigator.requestMIDIAccess().then( this.onMIDISuccess.bind(this), this.onMIDIFailure.bind(this) ); +} + +MIDIInterface.input = null; + +MIDIInterface.MIDIEvent = MIDIEvent; + +MIDIInterface.prototype.onMIDISuccess = function(midiAccess) +{ + console.log( "MIDI ready!" ); + console.log( midiAccess ); + this.midi = midiAccess; // store in the global (in real usage, would probably keep in an object instance) + this.updatePorts(); + + if (this.on_ready) + this.on_ready(this); +} + +MIDIInterface.prototype.updatePorts = function() +{ + var midi = this.midi; + this.input_ports = midi.inputs; + var num = 0; + + var it = this.input_ports.values(); + var it_value = it.next(); + while( it_value && it_value.done === false ) + { + var port_info = it_value.value; + console.log( "Input port [type:'" + port_info.type + "'] id:'" + port_info.id + + "' manufacturer:'" + port_info.manufacturer + "' name:'" + port_info.name + + "' version:'" + port_info.version + "'" ); + num++; + it_value = it.next(); + } + this.num_input_ports = num; + + num = 0; + this.output_ports = midi.outputs; + var it = this.output_ports.values(); + var it_value = it.next(); + while( it_value && it_value.done === false ) + { + var port_info = it_value.value; + console.log( "Output port [type:'" + port_info.type + "'] id:'" + port_info.id + + "' manufacturer:'" + port_info.manufacturer + "' name:'" + port_info.name + + "' version:'" + port_info.version + "'" ); + num++; + it_value = it.next(); + } + this.num_output_ports = num; + + + /* OLD WAY + for (var i = 0; i < this.input_ports.size; ++i) { + var input = this.input_ports.get(i); + if(!input) + continue; //sometimes it is null?! + console.log( "Input port [type:'" + input.type + "'] id:'" + input.id + + "' manufacturer:'" + input.manufacturer + "' name:'" + input.name + + "' version:'" + input.version + "'" ); + num++; + } + this.num_input_ports = num; + + + num = 0; + this.output_ports = midi.outputs; + for (var i = 0; i < this.output_ports.size; ++i) { + var output = this.output_ports.get(i); + if(!output) + continue; + console.log( "Output port [type:'" + output.type + "'] id:'" + output.id + + "' manufacturer:'" + output.manufacturer + "' name:'" + output.name + + "' version:'" + output.version + "'" ); + num++; + } + this.num_output_ports = num; + */ +} + +MIDIInterface.prototype.onMIDIFailure = function(msg) +{ + console.error( "Failed to get MIDI access - " + msg ); +} + +MIDIInterface.prototype.openInputPort = function( port, callback ) +{ + var input_port = this.input_ports.get( "input-" + port ); + if(!input_port) + return false; + MIDIInterface.input = this; + var that = this; + + input_port.onmidimessage = function(a) { + var midi_event = new MIDIEvent(a.data); + that.updateState( midi_event ); + if(callback) + callback(a.data, midi_event ); + if(MIDIInterface.on_message) + MIDIInterface.on_message( a.data, midi_event ); + } + console.log("port open: ", input_port); + return true; +} + +MIDIInterface.parseMsg = function(data) +{ + +} + +MIDIInterface.prototype.updateState = function( midi_event ) +{ + switch( midi_event.cmd ) + { + case MIDIEvent.NOTEON: this.state.note[ midi_event.value1|0 ] = midi_event.value2; break; + case MIDIEvent.NOTEOFF: this.state.note[ midi_event.value1|0 ] = 0; break; + case MIDIEvent.CONTROLLERCHANGE: this.state.cc[ midi_event.getCC() ] = midi_event.getCCValue(); break; + } +} + +MIDIInterface.prototype.sendMIDI = function( port, midi_data ) +{ + if( !midi_data ) + return; + + var output_port = this.output_ports.get( "output-" + port ); + if(!output_port) + return; + + MIDIInterface.output = this; + + if( midi_data.constructor === MIDIEvent) + output_port.send( midi_data.data ); + else + output_port.send( midi_data ); +} + + + +function LGMIDIIn() +{ + this.addOutput( "on_midi", LiteGraph.EVENT ); + this.addOutput( "out", "midi" ); + this.properties = {port: 0}; + this._last_midi_event = null; + this._current_midi_event = null; + + var that = this; + new MIDIInterface( function( midi ){ + //open + that._midi = midi; + if(that._waiting) + that.onStart(); + that._waiting = false; + }); +} + +LGMIDIIn.MIDIInterface = MIDIInterface; + +LGMIDIIn.title = "MIDI Input"; +LGMIDIIn.desc = "Reads MIDI from a input port"; + +LGMIDIIn.prototype.getPropertyInfo = function(name) +{ + if(!this._midi) + return; + + if(name == "port") + { + var values = {}; + for (var i = 0; i < this._midi.input_ports.size; ++i) + { + var input = this._midi.input_ports.get( "input-" + i); + values[i] = i + ".- " + input.name + " version:" + input.version; + } + return { type: "enum", values: values }; + } +} + +LGMIDIIn.prototype.onStart = function() +{ + if(this._midi) + this._midi.openInputPort( this.properties.port, this.onMIDIEvent.bind(this) ); + else + this._waiting = true; +} + +LGMIDIIn.prototype.onMIDIEvent = function( data, midi_event ) +{ + this._last_midi_event = midi_event; + + this.trigger( "on_midi", midi_event ); + if(midi_event.cmd == MIDIEvent.NOTEON) + this.trigger( "on_noteon", midi_event ); + else if(midi_event.cmd == MIDIEvent.NOTEOFF) + this.trigger( "on_noteoff", midi_event ); + else if(midi_event.cmd == MIDIEvent.CONTROLLERCHANGE) + this.trigger( "on_cc", midi_event ); + else if(midi_event.cmd == MIDIEvent.PROGRAMCHANGE) + this.trigger( "on_pc", midi_event ); + else if(midi_event.cmd == MIDIEvent.PITCHBEND) + this.trigger( "on_pitchbend", midi_event ); +} + +LGMIDIIn.prototype.onExecute = function() +{ + if(this.outputs) + { + var last = this._last_midi_event; + for(var i = 0; i < this.outputs.length; ++i) + { + var output = this.outputs[i]; + var v = null; + switch (output.name) + { + case "midi": v = this._midi; break; + case "last_midi": v = last; break; + default: + continue; + } + this.setOutputData( i, v ); + } + } +} + +LGMIDIIn.prototype.onGetOutputs = function() { + return [ + ["last_midi","midi"], + ["on_midi",LiteGraph.EVENT], + ["on_noteon",LiteGraph.EVENT], + ["on_noteoff",LiteGraph.EVENT], + ["on_cc",LiteGraph.EVENT], + ["on_pc",LiteGraph.EVENT], + ["on_pitchbend",LiteGraph.EVENT] + ]; +} + +LiteGraph.registerNodeType("midi/input", LGMIDIIn); + + +function LGMIDIOut() +{ + this.addInput( "send", LiteGraph.EVENT ); + this.properties = {port: 0}; + + var that = this; + new MIDIInterface( function( midi ){ + that._midi = midi; + }); +} + +LGMIDIOut.MIDIInterface = MIDIInterface; + +LGMIDIOut.title = "MIDI Output"; +LGMIDIOut.desc = "Sends MIDI to output channel"; + +LGMIDIOut.prototype.getPropertyInfo = function(name) +{ + if(!this._midi) + return; + + if(name == "port") + { + var values = {}; + for (var i = 0; i < this._midi.output_ports.size; ++i) + { + var output = this._midi.output_ports.get(i); + values[i] = i + ".- " + output.name + " version:" + output.version; + } + return { type: "enum", values: values }; + } +} + + +LGMIDIOut.prototype.onAction = function(event, midi_event ) +{ + console.log(midi_event); + if(!this._midi) + return; + if(event == "send") + this._midi.sendMIDI( this.port, midi_event ); + this.trigger("midi",midi_event); +} + +LGMIDIOut.prototype.onGetInputs = function() { + return [["send",LiteGraph.ACTION]]; +} + +LGMIDIOut.prototype.onGetOutputs = function() { + return [["on_midi",LiteGraph.EVENT]]; +} + +LiteGraph.registerNodeType("midi/output", LGMIDIOut); + + +function LGMIDIShow() +{ + this.addInput( "on_midi", LiteGraph.EVENT ); + this._str = ""; + this.size = [200,40] +} + +LGMIDIShow.title = "MIDI Show"; +LGMIDIShow.desc = "Shows MIDI in the graph"; + +LGMIDIShow.prototype.onAction = function(event, midi_event ) +{ + if(!midi_event) + return; + if(midi_event.constructor === MIDIEvent) + this._str = midi_event.toString(); + else + this._str = "???"; +} + +LGMIDIShow.prototype.onDrawForeground = function( ctx ) +{ + if( !this._str ) + return; + + ctx.font = "30px Arial"; + ctx.fillText( this._str, 10, this.size[1] * 0.8 ); +} + +LGMIDIShow.prototype.onGetInputs = function() { + return [["in",LiteGraph.ACTION]]; +} + +LGMIDIShow.prototype.onGetOutputs = function() { + return [["on_midi",LiteGraph.EVENT]]; +} + +LiteGraph.registerNodeType("midi/show", LGMIDIShow); + + + +function LGMIDIFilter() +{ + this.properties = { + channel: -1, + cmd: -1, + min_value: -1, + max_value: -1 + }; + + this.addInput( "in", LiteGraph.EVENT ); + this.addOutput( "on_midi", LiteGraph.EVENT ); +} + +LGMIDIFilter.title = "MIDI Filter"; +LGMIDIFilter.desc = "Filters MIDI messages"; + +LGMIDIFilter.prototype.onAction = function(event, midi_event ) +{ + if(!midi_event || midi_event.constructor !== MIDIEvent) + return; + + if( this.properties.channel != -1 && midi_event.channel != this.properties.channel) + return; + if(this.properties.cmd != -1 && midi_event.cmd != this.properties.cmd) + return; + if(this.properties.min_value != -1 && midi_event.data[1] < this.properties.min_value) + return; + if(this.properties.max_value != -1 && midi_event.data[1] > this.properties.max_value) + return; + this.trigger("on_midi",midi_event); +} + +LiteGraph.registerNodeType("midi/filter", LGMIDIFilter); + + +function LGMIDIEvent() +{ + this.properties = { + channel: 0, + cmd: "CC", + value1: 1, + value2: 1 + }; + + this.addInput( "send", LiteGraph.EVENT ); + this.addInput( "assign", LiteGraph.EVENT ); + this.addOutput( "on_midi", LiteGraph.EVENT ); +} + +LGMIDIEvent.title = "MIDIEvent"; +LGMIDIEvent.desc = "Create a MIDI Event"; + +LGMIDIEvent.prototype.onAction = function( event, midi_event ) +{ + if(event == "assign") + { + this.properties.channel = midi_event.channel; + this.properties.cmd = midi_event.cmd; + this.properties.value1 = midi_event.data[1]; + this.properties.value2 = midi_event.data[2]; + return; + } + + //send + var midi_event = new MIDIEvent(); + midi_event.channel = this.properties.channel; + if(this.properties.cmd && this.properties.cmd.constructor === String) + midi_event.setCommandFromString( this.properties.cmd ); + else + midi_event.cmd = this.properties.cmd; + midi_event.data[0] = midi_event.cmd | midi_event.channel; + midi_event.data[1] = Number(this.properties.value1); + midi_event.data[2] = Number(this.properties.value2); + this.trigger("on_midi",midi_event); +} + +LGMIDIEvent.prototype.onExecute = function() +{ + var props = this.properties; + + if(this.outputs) + { + for(var i = 0; i < this.outputs.length; ++i) + { + var output = this.outputs[i]; + var v = null; + switch (output.name) + { + case "midi": + v = new MIDIEvent(); + v.setup([ props.cmd, props.value1, props.value2 ]); + v.channel = props.channel; + break; + case "command": v = props.cmd; break; + case "cc": v = props.value1; break; + case "cc_value": v = props.value2; break; + case "note": v = (props.cmd == MIDIEvent.NOTEON || props.cmd == MIDIEvent.NOTEOFF) ? props.value1 : null; break; + case "velocity": v = props.cmd == MIDIEvent.NOTEON ? props.value2 : null; break; + case "pitch": v = props.cmd == MIDIEvent.NOTEON ? MIDIEvent.computePitch( props.value1 ) : null; break; + case "pitchbend": v = props.cmd == MIDIEvent.PITCHBEND ? MIDIEvent.computePitchBend( props.value1, props.value2 ) : null; break; + default: + continue; + } + if(v !== null) + this.setOutputData( i, v ); + } + } +} + +LGMIDIEvent.prototype.onPropertyChanged = function(name,value) +{ + if(name == "cmd") + this.properties.cmd = MIDIEvent.computeCommandFromString( value ); +} + + +LGMIDIEvent.prototype.onGetOutputs = function() { + return [ + ["midi","midi"], + ["on_midi",LiteGraph.EVENT], + ["command","number"], + ["note","number"], + ["velocity","number"], + ["cc","number"], + ["cc_value","number"], + ["pitch","number"], + ["pitchbend","number"] + ]; +} + + +LiteGraph.registerNodeType("midi/event", LGMIDIEvent); + + +function LGMIDICC() +{ + this.properties = { +// channel: 0, + cc: 1, + value: 0 + }; + + this.addOutput( "value", "number" ); +} + +LGMIDICC.title = "MIDICC"; +LGMIDICC.desc = "gets a Controller Change"; + +LGMIDICC.prototype.onExecute = function() +{ + var props = this.properties; + if( MIDIInterface.input ) + this.properties.value = MIDIInterface.input.state.cc[ this.properties.cc ]; + this.setOutputData( 0, this.properties.value ); +} + +LiteGraph.registerNodeType("midi/cc", LGMIDICC); + + + + +function now() { return window.performance.now() } + +})( this ); +(function( global ) +{ +var LiteGraph = global.LiteGraph; + +var LGAudio = {}; +global.LGAudio = LGAudio; + +LGAudio.getAudioContext = function() +{ + if(!this._audio_context) + { + window.AudioContext = window.AudioContext || window.webkitAudioContext; + if(!window.AudioContext) + { + console.error("AudioContext not supported by browser"); + return null; + } + this._audio_context = new AudioContext(); + this._audio_context.onmessage = function(msg) { console.log("msg",msg);}; + this._audio_context.onended = function(msg) { console.log("ended",msg);}; + this._audio_context.oncomplete = function(msg) { console.log("complete",msg);}; + } + + //in case it crashes + //if(this._audio_context.state == "suspended") + // this._audio_context.resume(); + return this._audio_context; +} + +LGAudio.connect = function( audionodeA, audionodeB ) +{ + try + { + audionodeA.connect( audionodeB ); + } + catch (err) + { + console.warn("LGraphAudio:",err); + } +} + +LGAudio.disconnect = function( audionodeA, audionodeB ) +{ + try + { + audionodeA.disconnect( audionodeB ); + } + catch (err) + { + console.warn("LGraphAudio:",err); + } +} + +LGAudio.changeAllAudiosConnections = function( node, connect ) +{ + if(node.inputs) + { + for(var i = 0; i < node.inputs.length; ++i) + { + var input = node.inputs[i]; + var link_info = node.graph.links[ input.link ]; + if(!link_info) + continue; + + var origin_node = node.graph.getNodeById( link_info.origin_id ); + var origin_audionode = null; + if( origin_node.getAudioNodeInOutputSlot ) + origin_audionode = origin_node.getAudioNodeInOutputSlot( link_info.origin_slot ); + else + origin_audionode = origin_node.audionode; + + var target_audionode = null; + if( node.getAudioNodeInInputSlot ) + target_audionode = node.getAudioNodeInInputSlot( i ); + else + target_audionode = node.audionode; + + if(connect) + LGAudio.connect( origin_audionode, target_audionode ); + else + LGAudio.disconnect( origin_audionode, target_audionode ); + } + } + + if(node.outputs) + { + for(var i = 0; i < node.outputs.length; ++i) + { + var output = node.outputs[i]; + for(var j = 0; j < output.links.length; ++j) + { + var link_info = node.graph.links[ output.links[j] ]; + if(!link_info) + continue; + + var origin_audionode = null; + if( node.getAudioNodeInOutputSlot ) + origin_audionode = node.getAudioNodeInOutputSlot( i ); + else + origin_audionode = node.audionode; + + var target_node = node.graph.getNodeById( link_info.target_id ); + var target_audionode = null; + if( target_node.getAudioNodeInInputSlot ) + target_audionode = target_node.getAudioNodeInInputSlot( link_info.target_slot ); + else + target_audionode = target_node.audionode; + + if(connect) + LGAudio.connect( origin_audionode, target_audionode ); + else + LGAudio.disconnect( origin_audionode, target_audionode ); + } + } + } +} + +//used by many nodes +LGAudio.onConnectionsChange = function( connection, slot, connected, link_info ) +{ + //only process the outputs events + if(connection != LiteGraph.OUTPUT) + return; + + var target_node = null; + if( link_info ) + target_node = this.graph.getNodeById( link_info.target_id ); + + if( !target_node ) + return; + + //get origin audionode + var local_audionode = null; + if(this.getAudioNodeInOutputSlot) + local_audionode = this.getAudioNodeInOutputSlot( slot ); + else + local_audionode = this.audionode; + + //get target audionode + var target_audionode = null; + if(target_node.getAudioNodeInInputSlot) + target_audionode = target_node.getAudioNodeInInputSlot( link_info.target_slot ); + else + target_audionode = target_node.audionode; + + //do the connection/disconnection + if( connected ) + LGAudio.connect( local_audionode, target_audionode ); + else + LGAudio.disconnect( local_audionode, target_audionode ); +} + +//this function helps creating wrappers to existing classes +LGAudio.createAudioNodeWrapper = function( class_object ) +{ + var old_func = class_object.prototype.onPropertyChanged; + + class_object.prototype.onPropertyChanged = function(name, value) + { + if(old_func) + old_func.call(this,name,value); + + if(!this.audionode) + return; + + if( this.audionode[ name ] === undefined ) + return; + + if( this.audionode[ name ].value !== undefined ) + this.audionode[ name ].value = value; + else + this.audionode[ name ] = value; + } + + class_object.prototype.onConnectionsChange = LGAudio.onConnectionsChange; +} + +//contains the samples decoded of the loaded audios in AudioBuffer format +LGAudio.cached_audios = {}; + +LGAudio.loadSound = function( url, on_complete, on_error ) +{ + if( LGAudio.cached_audios[ url ] && url.indexOf("blob:") == -1 ) + { + if(on_complete) + on_complete( LGAudio.cached_audios[ url ] ); + return; + } + + if( LGAudio.onProcessAudioURL ) + url = LGAudio.onProcessAudioURL( url ); + + //load new sample + var request = new XMLHttpRequest(); + request.open('GET', url, true); + request.responseType = 'arraybuffer'; + + var context = LGAudio.getAudioContext(); + + // Decode asynchronously + request.onload = function() { + console.log("AudioSource loaded"); + context.decodeAudioData( request.response, function( buffer ) { + console.log("AudioSource decoded"); + LGAudio.cached_audios[ url ] = buffer; + if(on_complete) + on_complete( buffer ); + }, onError); + } + request.send(); + + function onError(err) + { + console.log("Audio loading sample error:",err); + if(on_error) + on_error(err); + } + + return request; +} + + +//**************************************************** + +function LGAudioSource() +{ + this.properties = { + src: "", + gain: 0.5, + loop: true, + autoplay: true, + playbackRate: 1 + }; + + this._loading_audio = false; + this._audiobuffer = null; //points to AudioBuffer with the audio samples decoded + this._audionodes = []; + this._last_sourcenode = null; //the last AudioBufferSourceNode (there could be more if there are several sounds playing) + + this.addOutput( "out", "audio" ); + this.addInput( "gain", "number" ); + + //init context + var context = LGAudio.getAudioContext(); + + //create gain node to control volume + this.audionode = context.createGain(); + this.audionode.graphnode = this; + this.audionode.gain.value = this.properties.gain; + + //debug + if(this.properties.src) + this.loadSound( this.properties.src ); +} + +LGAudioSource["@src"] = { widget: "resource" }; +LGAudioSource.supported_extensions = ["wav","ogg","mp3"]; + + +LGAudioSource.prototype.onAdded = function(graph) +{ + if(graph.status === LGraph.STATUS_RUNNING) + this.onStart(); +} + +LGAudioSource.prototype.onStart = function() +{ + if(!this._audiobuffer) + return; + + if(this.properties.autoplay) + this.playBuffer( this._audiobuffer ); +} + +LGAudioSource.prototype.onStop = function() +{ + this.stopAllSounds(); +} + +LGAudioSource.prototype.onPause = function() +{ + this.pauseAllSounds(); +} + +LGAudioSource.prototype.onUnpause = function() +{ + this.unpauseAllSounds(); + //this.onStart(); +} + + +LGAudioSource.prototype.onRemoved = function() +{ + this.stopAllSounds(); + if(this._dropped_url) + URL.revokeObjectURL( this._url ); +} + +LGAudioSource.prototype.stopAllSounds = function() +{ + //iterate and stop + for(var i = 0; i < this._audionodes.length; ++i ) + { + if(this._audionodes[i].started) + { + this._audionodes[i].started = false; + this._audionodes[i].stop(); + } + //this._audionodes[i].disconnect( this.audionode ); + } + this._audionodes.length = 0; +} + +LGAudioSource.prototype.pauseAllSounds = function() +{ + LGAudio.getAudioContext().suspend(); +} + +LGAudioSource.prototype.unpauseAllSounds = function() +{ + LGAudio.getAudioContext().resume(); +} + +LGAudioSource.prototype.onExecute = function() +{ + if(this.inputs) + for(var i = 0; i < this.inputs.length; ++i) + { + var input = this.inputs[i]; + if(input.link == null) + continue; + var v = this.getInputData(i); + if( v === undefined ) + continue; + if( input.name == "gain" ) + this.audionode.gain.value = v; + else if( input.name == "playbackRate" ) + { + this.properties.playbackRate = v; + for(var j = 0; j < this._audionodes.length; ++j) + this._audionodes[j].playbackRate.value = v; + } + } + + if(this.outputs) + for(var i = 0; i < this.outputs.length; ++i) + { + var output = this.outputs[i]; + if( output.name == "buffer" && this._audiobuffer ) + this.setOutputData( i, this._audiobuffer ); + } +} + +LGAudioSource.prototype.onAction = function(event) +{ + if(this._audiobuffer) + { + if(event == "Play") + this.playBuffer(this._audiobuffer); + else if(event == "Stop") + this.stopAllSounds(); + } +} + +LGAudioSource.prototype.onPropertyChanged = function( name, value ) +{ + if( name == "src" ) + this.loadSound( value ); + else if(name == "gain") + this.audionode.gain.value = value; + else if(name == "playbackRate") + { + for(var j = 0; j < this._audionodes.length; ++j) + this._audionodes[j].playbackRate.value = value; + } +} + +LGAudioSource.prototype.playBuffer = function( buffer ) +{ + var that = this; + var context = LGAudio.getAudioContext(); + + //create a new audionode (this is mandatory, AudioAPI doesnt like to reuse old ones) + var audionode = context.createBufferSource(); //create a AudioBufferSourceNode + this._last_sourcenode = audionode; + audionode.graphnode = this; + audionode.buffer = buffer; + audionode.loop = this.properties.loop; + audionode.playbackRate.value = this.properties.playbackRate; + this._audionodes.push( audionode ); + audionode.connect( this.audionode ); //connect to gain + this._audionodes.push( audionode ); + + audionode.onended = function() + { + //console.log("ended!"); + that.trigger("ended"); + //remove + var index = that._audionodes.indexOf( audionode ); + if(index != -1) + that._audionodes.splice(index,1); + } + + if(!audionode.started) + { + audionode.started = true; + audionode.start(); + } + return audionode; +} + +LGAudioSource.prototype.loadSound = function( url ) +{ + var that = this; + + //kill previous load + if(this._request) + { + this._request.abort(); + this._request = null; + } + + this._audiobuffer = null; //points to the audiobuffer once the audio is loaded + this._loading_audio = false; + + if(!url) + return; + + this._request = LGAudio.loadSound( url, inner ); + + this._loading_audio = true; + this.boxcolor = "#AA4"; + + function inner( buffer ) + { + this.boxcolor = LiteGraph.NODE_DEFAULT_BOXCOLOR; + that._audiobuffer = buffer; + that._loading_audio = false; + //if is playing, then play it + if(that.graph && that.graph.status === LGraph.STATUS_RUNNING) + that.onStart(); //this controls the autoplay already + } +} + +//Helps connect/disconnect AudioNodes when new connections are made in the node +LGAudioSource.prototype.onConnectionsChange = LGAudio.onConnectionsChange; + +LGAudioSource.prototype.onGetInputs = function() +{ + return [["playbackRate","number"],["Play",LiteGraph.ACTION],["Stop",LiteGraph.ACTION]]; +} + +LGAudioSource.prototype.onGetOutputs = function() +{ + return [["buffer","audiobuffer"],["ended",LiteGraph.EVENT]]; +} + +LGAudioSource.prototype.onDropFile = function(file) +{ + if(this._dropped_url) + URL.revokeObjectURL( this._dropped_url ); + var url = URL.createObjectURL( file ); + this.properties.src = url; + this.loadSound( url ); + this._dropped_url = url; +} + + +LGAudioSource.title = "Source"; +LGAudioSource.desc = "Plays audio"; +LiteGraph.registerNodeType("audio/source", LGAudioSource); + + +//***************************************************** + +function LGAudioAnalyser() +{ + this.properties = { + fftSize: 2048, + minDecibels: -100, + maxDecibels: -10, + smoothingTimeConstant: 0.5 + }; + + var context = LGAudio.getAudioContext(); + + this.audionode = context.createAnalyser(); + this.audionode.graphnode = this; + this.audionode.fftSize = this.properties.fftSize; + this.audionode.minDecibels = this.properties.minDecibels; + this.audionode.maxDecibels = this.properties.maxDecibels; + this.audionode.smoothingTimeConstant = this.properties.smoothingTimeConstant; + + this.addInput("in","audio"); + this.addOutput("freqs","array"); + this.addOutput("samples","array"); + + this._freq_bin = null; + this._time_bin = null; +} + +LGAudioAnalyser.prototype.onPropertyChanged = function(name, value) +{ + this.audionode[ name ] = value; +} + +LGAudioAnalyser.prototype.onExecute = function() +{ + if(this.isOutputConnected(0)) + { + //send FFT + var bufferLength = this.audionode.frequencyBinCount; + if( !this._freq_bin || this._freq_bin.length != bufferLength ) + this._freq_bin = new Uint8Array( bufferLength ); + this.audionode.getByteFrequencyData( this._freq_bin ); + this.setOutputData(0,this._freq_bin); + } + + //send analyzer + if(this.isOutputConnected(1)) + { + //send Samples + var bufferLength = this.audionode.frequencyBinCount; + if( !this._time_bin || this._time_bin.length != bufferLength ) + this._time_bin = new Uint8Array( bufferLength ); + this.audionode.getByteTimeDomainData( this._time_bin ); + this.setOutputData(1,this._time_bin); + } + + + //properties + for(var i = 1; i < this.inputs.length; ++i) + { + var input = this.inputs[i]; + if(input.link == null) + continue; + var v = this.getInputData(i); + if (v !== undefined) + this.audionode[ input.name ].value = v; + } + + + + //time domain + //this.audionode.getFloatTimeDomainData( dataArray ); +} + +LGAudioAnalyser.prototype.onGetInputs = function() +{ + return [["minDecibels","number"],["maxDecibels","number"],["smoothingTimeConstant","number"]]; +} + +LGAudioAnalyser.prototype.onGetOutputs = function() +{ + return [["freqs","array"],["samples","array"]]; +} + + +LGAudioAnalyser.title = "Analyser"; +LGAudioAnalyser.desc = "Audio Analyser"; +LiteGraph.registerNodeType( "audio/analyser", LGAudioAnalyser ); + +//***************************************************** + +function LGAudioGain() +{ + //default + this.properties = { + gain: 1 + }; + + this.audionode = LGAudio.getAudioContext().createGain(); + this.addInput("in","audio"); + this.addInput("gain","number"); + this.addOutput("out","audio"); +} + +LGAudioGain.prototype.onExecute = function() +{ + if(!this.inputs || !this.inputs.length) + return; + + for(var i = 1; i < this.inputs.length; ++i) + { + var input = this.inputs[i]; + var v = this.getInputData(i); + if(v !== undefined) + this.audionode[ input.name ].value = v; + } +} + +LGAudio.createAudioNodeWrapper( LGAudioGain ); + +LGAudioGain.title = "Gain"; +LGAudioGain.desc = "Audio gain"; +LiteGraph.registerNodeType("audio/gain", LGAudioGain); + + +function LGAudioConvolver() +{ + //default + this.properties = { + impulse_src:"", + normalize: true + }; + + this.audionode = LGAudio.getAudioContext().createConvolver(); + this.addInput("in","audio"); + this.addOutput("out","audio"); +} + +LGAudio.createAudioNodeWrapper( LGAudioConvolver ); + +LGAudioConvolver.prototype.onRemove = function() +{ + if(this._dropped_url) + URL.revokeObjectURL( this._dropped_url ); +} + +LGAudioConvolver.prototype.onPropertyChanged = function( name, value ) +{ + if( name == "impulse_src" ) + this.loadImpulse( value ); + else if( name == "normalize" ) + this.audionode.normalize = value; +} + +LGAudioConvolver.prototype.onDropFile = function(file) +{ + if(this._dropped_url) + URL.revokeObjectURL( this._dropped_url ); + this._dropped_url = URL.createObjectURL( file ); + this.properties.impulse_src = this._dropped_url; + this.loadImpulse( this._dropped_url ); +} + +LGAudioConvolver.prototype.loadImpulse = function( url ) +{ + var that = this; + + //kill previous load + if(this._request) + { + this._request.abort(); + this._request = null; + } + + this._impulse_buffer = null; + this._loading_impulse = false; + + if(!url) + return; + + //load new sample + this._request = LGAudio.loadSound( url, inner ); + this._loading_impulse = true; + + // Decode asynchronously + function inner( buffer ) { + that._impulse_buffer = buffer; + that.audionode.buffer = buffer; + console.log("Impulse signal set"); + that._loading_impulse = false; + } +} + +LGAudioConvolver.title = "Convolver"; +LGAudioConvolver.desc = "Convolves the signal (used for reverb)"; +LiteGraph.registerNodeType("audio/convolver", LGAudioConvolver); + + +function LGAudioDynamicsCompressor() +{ + //default + this.properties = { + threshold: -50, + knee: 40, + ratio: 12, + reduction: -20, + attack: 0, + release: 0.25 + }; + + this.audionode = LGAudio.getAudioContext().createDynamicsCompressor(); + this.addInput("in","audio"); + this.addOutput("out","audio"); +} + +LGAudio.createAudioNodeWrapper( LGAudioDynamicsCompressor ); + +LGAudioDynamicsCompressor.prototype.onExecute = function() +{ + if(!this.inputs || !this.inputs.length) + return; + for(var i = 1; i < this.inputs.length; ++i) + { + var input = this.inputs[i]; + if(input.link == null) + continue; + var v = this.getInputData(i); + if(v !== undefined) + this.audionode[ input.name ].value = v; + } +} + +LGAudioDynamicsCompressor.prototype.onGetInputs = function() +{ + return [["threshold","number"],["knee","number"],["ratio","number"],["reduction","number"],["attack","number"],["release","number"]]; +} + +LGAudioDynamicsCompressor.title = "DynamicsCompressor"; +LGAudioDynamicsCompressor.desc = "Dynamics Compressor"; +LiteGraph.registerNodeType("audio/dynamicsCompressor", LGAudioDynamicsCompressor); + + +function LGAudioWaveShaper() +{ + //default + this.properties = { + }; + + this.audionode = LGAudio.getAudioContext().createWaveShaper(); + this.addInput("in","audio"); + this.addInput("shape","waveshape"); + this.addOutput("out","audio"); +} + +LGAudioWaveShaper.prototype.onExecute = function() +{ + if(!this.inputs || !this.inputs.length) + return; + var v = this.getInputData(1); + if(v === undefined) + return; + this.audionode.curve = v; +} + +LGAudioWaveShaper.prototype.setWaveShape = function(shape) +{ + this.audionode.curve = shape; +} + +LGAudio.createAudioNodeWrapper( LGAudioWaveShaper ); + +/* disabled till I dont find a way to do a wave shape +LGAudioWaveShaper.title = "WaveShaper"; +LGAudioWaveShaper.desc = "Distortion using wave shape"; +LiteGraph.registerNodeType("audio/waveShaper", LGAudioWaveShaper); +*/ + +function LGAudioMixer() +{ + //default + this.properties = { + gain1: 0.5, + gain2: 0.5 + }; + + this.audionode = LGAudio.getAudioContext().createGain(); + + this.audionode1 = LGAudio.getAudioContext().createGain(); + this.audionode1.gain.value = this.properties.gain1; + this.audionode2 = LGAudio.getAudioContext().createGain(); + this.audionode2.gain.value = this.properties.gain2; + + this.audionode1.connect( this.audionode ); + this.audionode2.connect( this.audionode ); + + this.addInput("in1","audio"); + this.addInput("in1 gain","number"); + this.addInput("in2","audio"); + this.addInput("in2 gain","number"); + + this.addOutput("out","audio"); +} + +LGAudioMixer.prototype.getAudioNodeInInputSlot = function( slot ) +{ + if(slot == 0) + return this.audionode1; + else if(slot == 2) + return this.audionode2; +} + +LGAudioMixer.prototype.onPropertyChanged = function( name, value ) +{ + if( name == "gain1" ) + this.audionode1.gain.value = value; + else if( name == "gain2" ) + this.audionode2.gain.value = value; +} + + +LGAudioMixer.prototype.onExecute = function() +{ + if(!this.inputs || !this.inputs.length) + return; + + for(var i = 1; i < this.inputs.length; ++i) + { + var input = this.inputs[i]; + + if(input.link == null || input.type == "audio") + continue; + + var v = this.getInputData(i); + if(v === undefined) + continue; + + if(i == 1) + this.audionode1.gain.value = v; + else if(i == 3) + this.audionode2.gain.value = v; + } +} + +LGAudio.createAudioNodeWrapper( LGAudioMixer ); + +LGAudioMixer.title = "Mixer"; +LGAudioMixer.desc = "Audio mixer"; +LiteGraph.registerNodeType("audio/mixer", LGAudioMixer); + + +function LGAudioDelay() +{ + //default + this.properties = { + delayTime: 0.5 + }; + + this.audionode = LGAudio.getAudioContext().createDelay( 10 ); + this.audionode.delayTime.value = this.properties.delayTime; + this.addInput("in","audio"); + this.addInput("time","number"); + this.addOutput("out","audio"); +} + +LGAudio.createAudioNodeWrapper( LGAudioDelay ); + +LGAudioDelay.prototype.onExecute = function() +{ + var v = this.getInputData(1); + if(v !== undefined ) + this.audionode.delayTime.value = v; +} + +LGAudioDelay.title = "Delay"; +LGAudioDelay.desc = "Audio delay"; +LiteGraph.registerNodeType("audio/delay", LGAudioDelay); + + +function LGAudioBiquadFilter() +{ + //default + this.properties = { + frequency: 350, + detune: 0, + Q: 1 + }; + this.addProperty("type","lowpass","enum",{values:["lowpass","highpass","bandpass","lowshelf","highshelf","peaking","notch","allpass"]}); + + //create node + this.audionode = LGAudio.getAudioContext().createBiquadFilter(); + + //slots + this.addInput("in","audio"); + this.addOutput("out","audio"); +} + +LGAudioBiquadFilter.prototype.onExecute = function() +{ + if(!this.inputs || !this.inputs.length) + return; + + for(var i = 1; i < this.inputs.length; ++i) + { + var input = this.inputs[i]; + if(input.link == null) + continue; + var v = this.getInputData(i); + if(v !== undefined) + this.audionode[ input.name ].value = v; + } +} + +LGAudioBiquadFilter.prototype.onGetInputs = function() +{ + return [["frequency","number"],["detune","number"],["Q","number"]]; +} + +LGAudio.createAudioNodeWrapper( LGAudioBiquadFilter ); + +LGAudioBiquadFilter.title = "BiquadFilter"; +LGAudioBiquadFilter.desc = "Audio filter"; +LiteGraph.registerNodeType("audio/biquadfilter", LGAudioBiquadFilter); + + + + +function LGAudioOscillatorNode() +{ + //default + this.properties = { + frequency: 440, + detune: 0, + type: "sine" + }; + this.addProperty("type","sine","enum",{values:["sine","square","sawtooth","triangle","custom"]}); + + //create node + this.audionode = LGAudio.getAudioContext().createOscillator(); + + //slots + this.addOutput("out","audio"); +} + +LGAudioOscillatorNode.prototype.onStart = function() +{ + if(!this.audionode.started) + { + this.audionode.started = true; + this.audionode.start(); + } +} + +LGAudioOscillatorNode.prototype.onStop = function() +{ + if(this.audionode.started) + { + this.audionode.started = false; + this.audionode.stop(); + } +} + +LGAudioOscillatorNode.prototype.onPause = function() +{ + this.onStop(); +} + +LGAudioOscillatorNode.prototype.onUnpause = function() +{ + this.onStart(); +} + +LGAudioOscillatorNode.prototype.onExecute = function() +{ + if(!this.inputs || !this.inputs.length) + return; + + for(var i = 0; i < this.inputs.length; ++i) + { + var input = this.inputs[i]; + if(input.link == null) + continue; + var v = this.getInputData(i); + if(v !== undefined) + this.audionode[ input.name ].value = v; + } +} + +LGAudioOscillatorNode.prototype.onGetInputs = function() +{ + return [["frequency","number"],["detune","number"],["type","string"]]; +} + +LGAudio.createAudioNodeWrapper( LGAudioOscillatorNode ); + +LGAudioOscillatorNode.title = "Oscillator"; +LGAudioOscillatorNode.desc = "Oscillator"; +LiteGraph.registerNodeType("audio/oscillator", LGAudioOscillatorNode); + + +//***************************************************** + +//EXTRA + + +function LGAudioVisualization() +{ + this.properties = { + continuous: true, + mark: -1 + }; + + this.addInput("data","array"); + this.addInput("mark","number"); + this.size = [300,200]; + this._last_buffer = null; +} + +LGAudioVisualization.prototype.onExecute = function() +{ + this._last_buffer = this.getInputData(0); + var v = this.getInputData(1); + if(v !== undefined) + this.properties.mark = v; + this.setDirtyCanvas(true,false); +} + +LGAudioVisualization.prototype.onDrawForeground = function(ctx) +{ + if(!this._last_buffer) + return; + + var buffer = this._last_buffer; + + //delta represents how many samples we advance per pixel + var delta = buffer.length / this.size[0]; + var h = this.size[1]; + + ctx.fillStyle = "black"; + ctx.fillRect(0,0,this.size[0],this.size[1]); + ctx.strokeStyle = "white"; + ctx.beginPath(); + var x = 0; + + if(this.properties.continuous) + { + ctx.moveTo(x,h); + for(var i = 0; i < buffer.length; i+= delta) + { + ctx.lineTo(x,h - (buffer[i|0]/255) * h); + x++; + } + } + else + { + for(var i = 0; i < buffer.length; i+= delta) + { + ctx.moveTo(x+0.5,h); + ctx.lineTo(x+0.5,h - (buffer[i|0]/255) * h); + x++; + } + } + ctx.stroke(); + + if(this.properties.mark >= 0) + { + var samplerate = LGAudio.getAudioContext().sampleRate; + var binfreq = samplerate / buffer.length; + var x = 2 * (this.properties.mark / binfreq) / delta; + if(x >= this.size[0]) + x = this.size[0]-1; + ctx.strokeStyle = "red"; + ctx.beginPath(); + ctx.moveTo(x,h); + ctx.lineTo(x,0); + ctx.stroke(); + } +} + +LGAudioVisualization.title = "Visualization"; +LGAudioVisualization.desc = "Audio Visualization"; +LiteGraph.registerNodeType("audio/visualization", LGAudioVisualization); + + +function LGAudioBandSignal() +{ + //default + this.properties = { + band: 440, + amplitude: 1 + }; + + this.addInput("freqs","array"); + this.addOutput("signal","number"); +} + +LGAudioBandSignal.prototype.onExecute = function() +{ + this._freqs = this.getInputData(0); + if( !this._freqs ) + return; + + var band = this.properties.band; + var v = this.getInputData(1); + if(v !== undefined) + band = v; + + var samplerate = LGAudio.getAudioContext().sampleRate; + var binfreq = samplerate / this._freqs.length; + var index = 2 * (band / binfreq); + var v = 0; + if( index < 0 ) + v = this._freqs[ 0 ]; + if( index >= this._freqs.length ) + v = this._freqs[ this._freqs.length - 1]; + else + { + var pos = index|0; + var v0 = this._freqs[ pos ]; + var v1 = this._freqs[ pos+1 ]; + var f = index - pos; + v = v0 * (1-f) + v1 * f; + } + + this.setOutputData( 0, (v/255) * this.properties.amplitude ); +} + +LGAudioBandSignal.prototype.onGetInputs = function() +{ + return [["band","number"]]; +} + +LGAudioBandSignal.title = "Signal"; +LGAudioBandSignal.desc = "extract the signal of some frequency"; +LiteGraph.registerNodeType("audio/signal", LGAudioBandSignal); + + +function LGAudioScript() +{ + if(!LGAudioScript.default_code) + { + var code = LGAudioScript.default_function.toString(); + var index = code.indexOf("{")+1; + var index2 = code.lastIndexOf("}"); + LGAudioScript.default_code = code.substr(index, index2 - index); + } + + //default + this.properties = { + code: LGAudioScript.default_code + }; + + //create node + var ctx = LGAudio.getAudioContext(); + if(ctx.createScriptProcessor) + this.audionode = ctx.createScriptProcessor(4096,1,1); //buffer size, input channels, output channels + else + { + console.warn("ScriptProcessorNode deprecated"); + this.audionode = ctx.createGain(); //bypass audio + } + + this.processCode(); + if(!LGAudioScript._bypass_function) + LGAudioScript._bypass_function = this.audionode.onaudioprocess; + + //slots + this.addInput("in","audio"); + this.addOutput("out","audio"); +} + +LGAudioScript.prototype.onAdded = function( graph ) +{ + if(graph.status == LGraph.STATUS_RUNNING) + this.audionode.onaudioprocess = this._callback; +} + +LGAudioScript["@code"] = { widget: "code" }; + +LGAudioScript.prototype.onStart = function() +{ + this.audionode.onaudioprocess = this._callback; +} + +LGAudioScript.prototype.onStop = function() +{ + this.audionode.onaudioprocess = LGAudioScript._bypass_function; +} + +LGAudioScript.prototype.onPause = function() +{ + this.audionode.onaudioprocess = LGAudioScript._bypass_function; +} + +LGAudioScript.prototype.onUnpause = function() +{ + this.audionode.onaudioprocess = this._callback; +} + +LGAudioScript.prototype.onExecute = function() +{ + //nothing! because we need an onExecute to receive onStart... fix that +} + +LGAudioScript.prototype.onRemoved = function() +{ + this.audionode.onaudioprocess = LGAudioScript._bypass_function; +} + +LGAudioScript.prototype.processCode = function() +{ + try + { + var func = new Function( "properties", this.properties.code ); + this._script = new func( this.properties ); + this._old_code = this.properties.code; + this._callback = this._script.onaudioprocess; + } + catch (err) + { + console.error("Error in onaudioprocess code",err); + this._callback = LGAudioScript._bypass_function; + this.audionode.onaudioprocess = this._callback; + } +} + +LGAudioScript.prototype.onPropertyChanged = function( name, value ) +{ + if(name == "code") + { + this.properties.code = value; + this.processCode(); + if(this.graph && this.graph.status == LGraph.STATUS_RUNNING) + this.audionode.onaudioprocess = this._callback; + } +} + +LGAudioScript.default_function = function() +{ + +this.onaudioprocess = function(audioProcessingEvent) { + // The input buffer is the song we loaded earlier + var inputBuffer = audioProcessingEvent.inputBuffer; + + // The output buffer contains the samples that will be modified and played + var outputBuffer = audioProcessingEvent.outputBuffer; + + // Loop through the output channels (in this case there is only one) + for (var channel = 0; channel < outputBuffer.numberOfChannels; channel++) { + var inputData = inputBuffer.getChannelData(channel); + var outputData = outputBuffer.getChannelData(channel); + + // Loop through the 4096 samples + for (var sample = 0; sample < inputBuffer.length; sample++) { + // make output equal to the same as the input + outputData[sample] = inputData[sample]; + } + } +} + +} + +LGAudio.createAudioNodeWrapper( LGAudioScript ); + +LGAudioScript.title = "Script"; +LGAudioScript.desc = "apply script to signal"; +LiteGraph.registerNodeType("audio/script", LGAudioScript); + + +function LGAudioDestination() +{ + this.audionode = LGAudio.getAudioContext().destination; + this.addInput("in","audio"); +} + + +LGAudioDestination.title = "Destination"; +LGAudioDestination.desc = "Audio output"; +LiteGraph.registerNodeType("audio/destination", LGAudioDestination); + + + + +})( this ); +//event related nodes +(function(global){ +var LiteGraph = global.LiteGraph; + +function LGWebSocket() +{ + this.size = [60,20]; + this.addInput("send", LiteGraph.ACTION); + this.addOutput("received", LiteGraph.EVENT); + this.addInput("in", 0 ); + this.addOutput("out", 0 ); + this.properties = { + url: "", + room: "lgraph" //allows to filter messages + }; + this._ws = null; + this._last_data = []; +} + +LGWebSocket.title = "WebSocket"; +LGWebSocket.desc = "Send data through a websocket"; + +LGWebSocket.prototype.onPropertyChanged = function(name,value) +{ + if(name == "url") + this.createSocket(); +} + +LGWebSocket.prototype.onExecute = function() +{ + if(!this._ws && this.properties.url) + this.createSocket(); + + if(!this._ws || this._ws.readyState != WebSocket.OPEN ) + return; + + var room = this.properties.room; + + for(var i = 1; i < this.inputs.length; ++i) + { + var data = this.getInputData(i); + if(data != null) + { + var json; + try + { + json = JSON.stringify({ type: 0, room: room, channel: i, data: data }); + } + catch (err) + { + continue; + } + this._ws.send( json ); + } + } + + for(var i = 1; i < this.outputs.length; ++i) + this.setOutputData( i, this._last_data[i] ); +} + +LGWebSocket.prototype.createSocket = function() +{ + var that = this; + var url = this.properties.url; + if( url.substr(0,2) != "ws" ) + url = "ws://" + url; + this._ws = new WebSocket( url ); + this._ws.onopen = function() + { + console.log("ready"); + that.boxcolor = "#8E8"; + } + this._ws.onmessage = function(e) + { + var data = JSON.parse( e.data ); + if( data.room && data.room != this.properties.room ) + return; + if( e.data.type == 1 ) + that.triggerSlot( 0, data ); + else + that._last_data[ e.data.channel || 0 ] = data.data; + } + this._ws.onerror = function(e) + { + console.log("couldnt connect to websocket"); + that.boxcolor = "#E88"; + } + this._ws.onclose = function(e) + { + console.log("connection closed"); + that.boxcolor = "#000"; + } +} + +LGWebSocket.prototype.send = function(data) +{ + if(!this._ws || this._ws.readyState != WebSocket.OPEN ) + return; + this._ws.send( JSON.stringify({ type:1, msg: data }) ); +} + +LGWebSocket.prototype.onAction = function( action, param ) +{ + if(!this._ws || this._ws.readyState != WebSocket.OPEN ) + return; + this._ws.send( { type: 1, room: this.properties.room, action: action, data: param } ); +} + +LGWebSocket.prototype.onGetInputs = function() +{ + return [["in",0]]; +} + +LGWebSocket.prototype.onGetOutputs = function() +{ + return [["out",0]]; +} + +LiteGraph.registerNodeType("network/websocket", LGWebSocket ); + + +//It is like a websocket but using the SillyServer.js server that bounces packets back to all clients connected: +//For more information: https://github.com/jagenjo/SillyServer.js + +function LGSillyClient() +{ + this.size = [60,20]; + this.addInput("send", LiteGraph.ACTION); + this.addOutput("received", LiteGraph.EVENT); + this.addInput("in", 0 ); + this.addOutput("out", 0 ); + this.properties = { + url: "tamats.com:55000", + room: "lgraph", + save_bandwidth: true + }; + + this._server = null; + this.createSocket(); + this._last_input_data = []; + this._last_output_data = []; +} + +LGSillyClient.title = "SillyClient"; +LGSillyClient.desc = "Connects to SillyServer to broadcast messages"; + +LGSillyClient.prototype.onPropertyChanged = function(name,value) +{ + var final_url = (this.properties.url + "/" + this.properties.room); + if(this._server && this._final_url != final_url ) + { + this._server.connect( this.properties.url, this.properties.room ); + this._final_url = final_url; + } +} + +LGSillyClient.prototype.onExecute = function() +{ + if(!this._server || !this._server.is_connected) + return; + + var save_bandwidth = this.properties.save_bandwidth; + + for(var i = 1; i < this.inputs.length; ++i) + { + var data = this.getInputData(i); + if(data != null) + { + if( save_bandwidth && this._last_input_data[i] == data ) + continue; + this._server.sendMessage( { type: 0, channel: i, data: data } ); + this._last_input_data[i] = data; + } + } + + for(var i = 1; i < this.outputs.length; ++i) + this.setOutputData( i, this._last_output_data[i] ); +} + +LGSillyClient.prototype.createSocket = function() +{ + var that = this; + if(typeof(SillyClient) == "undefined") + { + if(!this._error) + console.error("SillyClient node cannot be used, you must include SillyServer.js"); + this._error = true; + return; + } + + this._server = new SillyClient(); + this._server.on_ready = function() + { + console.log("ready"); + that.boxcolor = "#8E8"; + } + this._server.on_message = function(id,msg) + { + var data = null; + try + { + data = JSON.parse( msg ); + } + catch (err) + { + return; + } + + if(data.type == 1) + that.triggerSlot( 0, data ); + else + that._last_output_data[ data.channel || 0 ] = data.data; + } + this._server.on_error = function(e) + { + console.log("couldnt connect to websocket"); + that.boxcolor = "#E88"; + } + this._server.on_close = function(e) + { + console.log("connection closed"); + that.boxcolor = "#000"; + } + + if(this.properties.url && this.properties.room) + { + this._server.connect( this.properties.url, this.properties.room ); + this._final_url = (this.properties.url + "/" + this.properties.room); + } +} + +LGSillyClient.prototype.send = function(data) +{ + if(!this._server || !this._server.is_connected) + return; + this._server.sendMessage( { type:1, data: data } ); +} + +LGSillyClient.prototype.onAction = function( action, param ) +{ + if(!this._server || !this._server.is_connected) + return; + this._server.sendMessage( { type: 1, action: action, data: param } ); +} + +LGSillyClient.prototype.onGetInputs = function() +{ + return [["in",0]]; +} + +LGSillyClient.prototype.onGetOutputs = function() +{ + return [["out",0]]; +} + +LiteGraph.registerNodeType("network/sillyclient", LGSillyClient ); + + })(this); \ No newline at end of file diff --git a/build/litegraph.min.js b/build/litegraph.min.js index 5545f9d03..0bd91832a 100755 --- a/build/litegraph.min.js +++ b/build/litegraph.min.js @@ -1,8024 +1,480 @@ -var $jscomp = $jscomp || {}; -$jscomp.scope = {}; -$jscomp.ASSUME_ES5 = !1; -$jscomp.ASSUME_NO_NATIVE_MAP = !1; -$jscomp.ASSUME_NO_NATIVE_SET = !1; -$jscomp.defineProperty = $jscomp.ASSUME_ES5 || "function" == typeof Object.defineProperties ? Object.defineProperty : function(u, f, k) { - u != Array.prototype && u != Object.prototype && (u[f] = k.value); -}; -$jscomp.getGlobal = function(u) { - return "undefined" != typeof window && window === u ? u : "undefined" != typeof global && null != global ? global : u; -}; -$jscomp.global = $jscomp.getGlobal(this); -$jscomp.polyfill = function(u, f, k, n) { - if (f) { - k = $jscomp.global; - u = u.split("."); - for (n = 0; n < u.length - 1; n++) { - var g = u[n]; - g in k || (k[g] = {}); - k = k[g]; - } - u = u[u.length - 1]; - n = k[u]; - f = f(n); - f != n && null != f && $jscomp.defineProperty(k, u, {configurable:!0, writable:!0, value:f}); - } -}; -$jscomp.polyfill("Array.prototype.fill", function(u) { - return u ? u : function(f, k, n) { - var g = this.length || 0; - 0 > k && (k = Math.max(0, g + k)); - if (null == n || n > g) { - n = g; - } - n = Number(n); - 0 > n && (n = Math.max(0, g + n)); - for (k = Number(k || 0); k < n; k++) { - this[k] = f; - } - return this; - }; -}, "es6", "es3"); -$jscomp.SYMBOL_PREFIX = "jscomp_symbol_"; -$jscomp.initSymbol = function() { - $jscomp.initSymbol = function() { - }; - $jscomp.global.Symbol || ($jscomp.global.Symbol = $jscomp.Symbol); -}; -$jscomp.Symbol = function() { - var u = 0; - return function(f) { - return $jscomp.SYMBOL_PREFIX + (f || "") + u++; - }; -}(); -$jscomp.initSymbolIterator = function() { - $jscomp.initSymbol(); - var u = $jscomp.global.Symbol.iterator; - u || (u = $jscomp.global.Symbol.iterator = $jscomp.global.Symbol("iterator")); - "function" != typeof Array.prototype[u] && $jscomp.defineProperty(Array.prototype, u, {configurable:!0, writable:!0, value:function() { - return $jscomp.arrayIterator(this); - }}); - $jscomp.initSymbolIterator = function() { - }; -}; -$jscomp.arrayIterator = function(u) { - var f = 0; - return $jscomp.iteratorPrototype(function() { - return f < u.length ? {done:!1, value:u[f++]} : {done:!0}; - }); -}; -$jscomp.iteratorPrototype = function(u) { - $jscomp.initSymbolIterator(); - u = {next:u}; - u[$jscomp.global.Symbol.iterator] = function() { - return this; - }; - return u; -}; -$jscomp.iteratorFromArray = function(u, f) { - $jscomp.initSymbolIterator(); - u instanceof String && (u += ""); - var k = 0, n = {next:function() { - if (k < u.length) { - var g = k++; - return {value:f(g, u[g]), done:!1}; - } - n.next = function() { - return {done:!0, value:void 0}; - }; - return n.next(); - }}; - n[Symbol.iterator] = function() { - return n; - }; - return n; -}; -$jscomp.polyfill("Array.prototype.values", function(u) { - return u ? u : function() { - return $jscomp.iteratorFromArray(this, function(f, k) { - return k; - }); - }; -}, "es8", "es3"); -(function(u) { - function f(a) { - l.debug && console.log("Graph created"); - this.list_of_graphcanvas = null; - this.clear(); - a && this.configure(a); - } - function k(a) { - this._ctor(a); - } - function n(a) { - this._ctor(a); - } - function g(a, b, e) { - e = e || {}; - this.background_image = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQBJREFUeNrs1rEKwjAUhlETUkj3vP9rdmr1Ysammk2w5wdxuLgcMHyptfawuZX4pJSWZTnfnu/lnIe/jNNxHHGNn//HNbbv+4dr6V+11uF527arU7+u63qfa/bnmh8sWLBgwYJlqRf8MEptXPBXJXa37BSl3ixYsGDBMliwFLyCV/DeLIMFCxYsWLBMwSt4Be/NggXLYMGCBUvBK3iNruC9WbBgwYJlsGApeAWv4L1ZBgsWLFiwYJmCV/AK3psFC5bBggULloJX8BpdwXuzYMGCBctgwVLwCl7Be7MMFixYsGDBsu8FH1FaSmExVfAxBa/gvVmwYMGCZbBg/W4vAQYA5tRF9QYlv/QAAAAASUVORK5CYII="; - a && a.constructor === String && (a = document.querySelector(a)); - this.max_zoom = 10; - this.min_zoom = 0.1; - this.zoom_modify_alpha = !0; - this.title_text_font = "bold " + l.NODE_TEXT_SIZE + "px Arial"; - this.inner_text_font = "normal " + l.NODE_SUBTEXT_SIZE + "px 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.use_gradients = !1; - 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_searchbox = this.allow_interaction = this.allow_dragnodes = this.allow_dragcanvas = this.show_info = !0; - this.drag_mode = !1; - this.filter = 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.canvas_mouse = [0, 0]; - this.onSearchBoxSelection = this.onSearchBox = null; - this.connections_width = 3; - this.round_radius = 8; - this.node_widget = this.current_node = null; - this.last_mouse_position = [0, 0]; - b && b.attachCanvas(this); - this.setCanvas(a); - this.clear(); - e.skip_render || this.startRendering(); - this.autoresize = e.autoresize; - } - function q(a, b) { - return Math.sqrt((b[0] - a[0]) * (b[0] - a[0]) + (b[1] - a[1]) * (b[1] - a[1])); - } - function r(a, b, e, c, h, m) { - return e < a && e + h > a && c < b && c + m > b ? !0 : !1; - } - function v(a, b) { - var e = a[0] + a[2], c = a[1] + a[3], h = b[1] + b[3]; - return a[0] > b[0] + b[2] || a[1] > h || e < b[0] || c < b[1] ? !1 : !0; - } - function w(a, b) { - function e(a) { - var b = parseInt(h.style.top); - h.style.top = (b + 0.1 * a.deltaY).toFixed() + "px"; - a.preventDefault(); - return !0; - } - this.options = b = b || {}; - var c = this; - b.parentMenu && (b.parentMenu.constructor !== this.constructor ? (console.error("parentMenu must be of class ContextMenu, ignoring it"), b.parentMenu = null) : (this.parentMenu = b.parentMenu, this.parentMenu.lock = !0, this.parentMenu.current_submenu = this)); - b.event && b.event.constructor !== MouseEvent && b.event.constructor !== CustomEvent && (console.error("Event passed to ContextMenu is not of type MouseEvent or CustomEvent. Ignoring it."), b.event = null); - var h = document.createElement("div"); - h.className = "litegraph litecontextmenu litemenubar-panel"; - h.style.minWidth = 100; - h.style.minHeight = 100; - h.style.pointerEvents = "none"; - setTimeout(function() { - h.style.pointerEvents = "auto"; - }, 100); - h.addEventListener("mouseup", function(a) { - a.preventDefault(); - return !0; - }, !0); - h.addEventListener("contextmenu", function(a) { - if (2 != a.button) { - return !1; - } - a.preventDefault(); - return !1; - }, !0); - h.addEventListener("mousedown", function(a) { - if (2 == a.button) { - return c.close(), a.preventDefault(), !0; - } - }, !0); - h.addEventListener("wheel", e, !0); - h.addEventListener("mousewheel", e, !0); - this.root = h; - if (b.title) { - var m = document.createElement("div"); - m.className = "litemenu-title"; - m.innerHTML = b.title; - h.appendChild(m); - } - m = 0; - for (var p in a) { - var g = a.constructor == Array ? a[p] : p; - null != g && g.constructor !== String && (g = void 0 === g.content ? String(g) : g.content); - this.addItem(g, a[p], b); - m++; - } - h.addEventListener("mouseleave", function(a) { - c.lock || c.close(a); - }); - a = document; - b.event && (a = b.event.target.ownerDocument); - a || (a = document); - a.body.appendChild(h); - p = b.left || 0; - a = b.top || 0; - b.event && (p = b.event.pageX - 10, a = b.event.pageY - 10, b.title && (a -= 20), b.parentMenu && (b = b.parentMenu.root.getBoundingClientRect(), p = b.left + b.width), b = document.body.getBoundingClientRect(), m = h.getBoundingClientRect(), p > b.width - m.width - 10 && (p = b.width - m.width - 10), a > b.height - m.height - 10 && (a = b.height - m.height - 10)); - h.style.left = p + "px"; - h.style.top = a + "px"; - } - var l = u.LiteGraph = {NODE_TITLE_HEIGHT:20, NODE_SLOT_HEIGHT:15, NODE_WIDGET_HEIGHT:20, NODE_WIDTH:140, NODE_MIN_WIDTH:50, NODE_COLLAPSED_RADIUS:10, NODE_COLLAPSED_WIDTH:80, CANVAS_GRID_SIZE:10, NODE_TITLE_COLOR:"#999", NODE_TEXT_SIZE:14, NODE_TEXT_COLOR:"#AAA", NODE_SUBTEXT_SIZE:12, NODE_DEFAULT_COLOR:"#333", NODE_DEFAULT_BGCOLOR:"#444", NODE_DEFAULT_BOXCOLOR:"#888", NODE_DEFAULT_SHAPE:"box", MAX_NUMBER_OF_NODES:1000, DEFAULT_POSITION:[100, 100], node_images_path:"", VALID_SHAPES:["default", - "box", "round", "card"], BOX_SHAPE:1, ROUND_SHAPE:2, CIRCLE_SHAPE:3, CARD_SHAPE:4, ARROW_SHAPE:5, INPUT:1, OUTPUT:2, EVENT:-1, ACTION:-1, ALWAYS:0, ON_EVENT:1, NEVER:2, ON_TRIGGER:3, NORMAL_TITLE:0, NO_TITLE:1, TRANSPARENT_TITLE:2, AUTOHIDE_TITLE: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 e = b.name, c = a.lastIndexOf("/"); - b.category = a.substr(0, c); - b.title || (b.title = e); - if (b.prototype) { - for (var h in k.prototype) { - b.prototype[h] || (b.prototype[h] = k.prototype[h]); - } - } - Object.defineProperty(b.prototype, "shape", {set:function(a) { - switch(a) { - case "default": - delete this._shape; - break; - case "box": - this._shape = l.BOX_SHAPE; - break; - case "round": - this._shape = l.ROUND_SHAPE; - break; - case "circle": - this._shape = l.CIRCLE_SHAPE; - break; - case "card": - this._shape = l.CARD_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[e] = 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, e, c) { - for (var h = Array(b.length), m = "", y = l.getParameterNames(b), g = 0; g < y.length; ++g) { - m += "this.addInput('" + y[g] + "'," + (e && e[g] ? "'" + e[g] + "'" : "0") + ");\n"; - } - e = Function(m + ("this.addOutput('out'," + (c ? "'" + c + "'" : 0) + ");\n")); - e.title = a.split("/").pop(); - e.desc = "Generated from " + b.name; - e.prototype.onExecute = function() { - for (var a = 0; a < h.length; ++a) { - h[a] = this.getInputData(a); - } - a = b.apply(this, h); - this.setOutputData(0, a); - }; - this.registerNodeType(a, e); - }, addNodeMethod:function(a, b) { - k.prototype[a] = b; - for (var e in this.registered_node_types) { - var c = this.registered_node_types[e]; - c.prototype[a] && (c.prototype["_" + a] = c.prototype[a]); - c.prototype[a] = b; - } - }, createNode:function(a, b, e) { - var c = this.registered_node_types[a]; - if (!c) { - return l.debug && console.log('GraphNode type "' + a + '" not registered.'), null; - } - b = b || c.title || a; - c = new c(b); - c.type = a; - !c.title && b && (c.title = b); - c.properties || (c.properties = {}); - c.properties_info || (c.properties_info = []); - c.flags || (c.flags = {}); - c.size || (c.size = c.computeSize()); - c.pos || (c.pos = l.DEFAULT_POSITION.concat()); - c.mode || (c.mode = l.ALWAYS); - if (e) { - for (var h in e) { - c[h] = e[h]; - } - } - return c; - }, getNodeType:function(a) { - return this.registered_node_types[a]; - }, getNodeTypesInCategory:function(a, b) { - var e = [], c; - for (c in this.registered_node_types) { - var h = this.registered_node_types[c]; - b && h.filter && h.filter != b || ("" == a ? null == h.category && e.push(h) : h.category == a && e.push(h)); - } - return e; - }, getNodeTypesCategories:function() { - var a = {"":1}, b; - for (b in this.registered_node_types) { - this.registered_node_types[b].category && !this.registered_node_types[b].skip_list && (a[this.registered_node_types[b].category] = 1); - } - var e = []; - for (b in a) { - e.push(b); - } - return e; - }, reloadNodes:function(a) { - var b = document.getElementsByTagName("script"), e = [], c; - for (c in b) { - e.push(b[c]); - } - b = document.getElementsByTagName("head")[0]; - a = document.location.href + a; - for (c in e) { - var h = e[c].src; - if (h && h.substr(0, a.length) == a) { - try { - l.debug && console.log("Reloading: " + h); - var m = document.createElement("script"); - m.type = "text/javascript"; - m.src = h; - b.appendChild(m); - b.removeChild(e[c]); - } catch (p) { - if (l.throw_errors) { - throw p; - } - l.debug && console.log("Error while reloading " + h); - } - } - } - l.debug && console.log("Nodes reloaded"); - }, cloneObject:function(a, b) { - if (null == a) { - return null; - } - a = JSON.parse(JSON.stringify(a)); - if (!b) { - return a; - } - for (var e in a) { - b[e] = a[e]; - } - return b; - }, isValidConnection:function(a, b) { - if (!a || !b || a == b || a == l.EVENT && b == l.ACTION) { - return !0; - } - a = String(a); - b = String(b); - a = a.toLowerCase(); - b = b.toLowerCase(); - if (-1 == a.indexOf(",") && -1 == b.indexOf(",")) { - return a == b; - } - a = a.split(","); - b = b.split(","); - for (var e = 0; e < a.length; ++e) { - for (var c = 0; c < b.length; ++c) { - if (a[e] == b[c]) { - return !0; - } - } - } - return !1; - }}; - l.getTime = "undefined" != typeof performance ? performance.now.bind(performance) : "undefined" != typeof Date && Date.now ? Date.now.bind(Date) : "undefined" != typeof process ? function() { - var a = process.hrtime(); - return 0.001 * a[0] + 1e-6 * a[1]; - } : function() { - return (new Date).getTime(); - }; - u.LGraph = l.LGraph = f; - f.supported_types = ["number", "string", "boolean"]; - f.prototype.getSupportedTypes = function() { - return this.supported_types || f.supported_types; - }; - f.STATUS_STOPPED = 1; - f.STATUS_RUNNING = 2; - f.prototype.clear = function() { - this.stop(); - this.status = f.STATUS_STOPPED; - this.last_link_id = this.last_node_id = 1; - this._version = -1; - this._nodes = []; - this._nodes_by_id = {}; - this._nodes_in_order = []; - this._nodes_executable = null; - this._groups = []; - this.links = {}; - this.iteration = 0; - this.config = {}; - this.fixedtime = this.runningtime = this.globaltime = 0; - this.elapsed_time = this.fixedtime_lapse = 0.01; - this.starttime = this.last_update_time = 0; - this.catch_errors = !0; - this.global_inputs = {}; - this.global_outputs = {}; - this.change(); - this.sendActionToCanvas("clear"); - }; - f.prototype.attachCanvas = function(a) { - if (a.constructor != g) { - throw "attachCanvas expects a LGraphCanvas instance"; - } - a.graph && a.graph != this && a.graph.detachCanvas(a); - a.graph = this; - this.list_of_graphcanvas || (this.list_of_graphcanvas = []); - this.list_of_graphcanvas.push(a); - }; - f.prototype.detachCanvas = function(a) { - if (this.list_of_graphcanvas) { - var b = this.list_of_graphcanvas.indexOf(a); - -1 != b && (a.graph = null, this.list_of_graphcanvas.splice(b, 1)); - } - }; - f.prototype.start = function(a) { - if (this.status != f.STATUS_RUNNING) { - this.status = f.STATUS_RUNNING; - if (this.onPlayEvent) { - this.onPlayEvent(); - } - this.sendEventToAllNodes("onStart"); - this.last_update_time = this.starttime = l.getTime(); - var b = this; - this.execution_timer_id = setInterval(function() { - b.runStep(1, !this.catch_errors); - }, a || 1); - } - }; - f.prototype.stop = function() { - if (this.status != f.STATUS_STOPPED) { - this.status = f.STATUS_STOPPED; - if (this.onStopEvent) { - this.onStopEvent(); - } - null != this.execution_timer_id && clearInterval(this.execution_timer_id); - this.execution_timer_id = null; - this.sendEventToAllNodes("onStop"); - } - }; - f.prototype.runStep = function(a, b) { - a = a || 1; - var e = l.getTime(); - this.globaltime = 0.001 * (e - this.starttime); - var c = this._nodes_executable ? this._nodes_executable : this._nodes; - if (c) { - if (b) { - for (var h = 0; h < a; h++) { - for (var m = 0, p = c.length; m < p; ++m) { - var g = c[m]; - if (g.mode == l.ALWAYS && g.onExecute) { - g.onExecute(); - } - } - this.fixedtime += this.fixedtime_lapse; - if (this.onExecuteStep) { - this.onExecuteStep(); - } - } - if (this.onAfterExecute) { - this.onAfterExecute(); - } - } else { - try { - for (h = 0; h < a; h++) { - m = 0; - for (p = c.length; m < p; ++m) { - if (g = c[m], g.mode == l.ALWAYS && g.onExecute) { - g.onExecute(); - } - } - this.fixedtime += this.fixedtime_lapse; - if (this.onExecuteStep) { - this.onExecuteStep(); - } - } - if (this.onAfterExecute) { - this.onAfterExecute(); - } - this.errors_in_execution = !1; - } catch (G) { - this.errors_in_execution = !0; - if (l.throw_errors) { - throw G; - } - l.debug && console.log("Error during execution: " + G); - this.stop(); - } - } - a = l.getTime(); - e = a - e; - 0 == e && (e = 1); - this.execution_time = 0.001 * e; - this.globaltime += 0.001 * e; - this.iteration += 1; - this.elapsed_time = 0.001 * (a - this.last_update_time); - this.last_update_time = a; - } - }; - f.prototype.updateExecutionOrder = function() { - this._nodes_in_order = this.computeExecutionOrder(!1); - this._nodes_executable = []; - for (var a = 0; a < this._nodes_in_order.length; ++a) { - this._nodes_in_order[a].onExecute && this._nodes_executable.push(this._nodes_in_order[a]); - } - }; - f.prototype.computeExecutionOrder = function(a, b) { - for (var e = [], c = [], h = {}, m = {}, p = {}, g = 0, d = this._nodes.length; g < d; ++g) { - var f = this._nodes[g]; - if (!a || f.onExecute) { - h[f.id] = f; - var n = 0; - if (f.inputs) { - for (var k = 0, q = f.inputs.length; k < q; k++) { - f.inputs[k] && null != f.inputs[k].link && (n += 1); - } - } - 0 == n ? (c.push(f), b && (f._level = 1)) : (b && (f._level = 0), p[f.id] = n); - } - } - for (; 0 != c.length;) { - if (f = c.shift(), e.push(f), delete h[f.id], f.outputs) { - for (g = 0; g < f.outputs.length; g++) { - if (a = f.outputs[g], null != a && null != a.links && 0 != a.links.length) { - for (k = 0; k < a.links.length; k++) { - (d = this.links[a.links[k]]) && !m[d.id] && (n = this.getNodeById(d.target_id), null == n ? m[d.id] = !0 : (b && (!n._level || n._level <= f._level) && (n._level = f._level + 1), m[d.id] = !0, --p[n.id], 0 == p[n.id] && c.push(n))); - } - } - } - } - } - for (g in h) { - e.push(h[g]); - } - e.length != this._nodes.length && l.debug && console.warn("something went wrong, nodes missing"); - d = e.length; - for (g = 0; g < d; ++g) { - e[g].order = g; - } - e = e.sort(function(a, b) { - var e = a.constructor.priority || a.priority || 0, c = b.constructor.priority || b.priority || 0; - return e == c ? a.order - b.order : e - c; - }); - for (g = 0; g < d; ++g) { - e[g].order = g; - } - return e; - }; - f.prototype.getAncestors = function(a) { - for (var b = [], e = [a], c = {}; e.length;) { - var h = e.shift(); - if (h.inputs) { - c[h.id] || h == a || (c[h.id] = !0, b.push(h)); - for (var m = 0; m < h.inputs.length; ++m) { - var p = h.getInputNode(m); - p && -1 == b.indexOf(p) && e.push(p); - } - } - } - b.sort(function(a, b) { - return a.order - b.order; - }); - return b; - }; - f.prototype.arrange = function(a) { - a = a || 40; - for (var b = this.computeExecutionOrder(!1, !0), e = [], c = 0; c < b.length; ++c) { - var h = b[c], m = h._level || 1; - e[m] || (e[m] = []); - e[m].push(h); - } - b = a; - for (c = 0; c < e.length; ++c) { - if (m = e[c]) { - for (var p = 100, g = a, d = 0; d < m.length; ++d) { - h = m[d], h.pos[0] = b, h.pos[1] = g, h.size[0] > p && (p = h.size[0]), g += h.size[1] + a; - } - b += p + a; - } - } - this.setDirtyCanvas(!0, !0); - }; - f.prototype.getTime = function() { - return this.globaltime; - }; - f.prototype.getFixedTime = function() { - return this.fixedtime; - }; - f.prototype.getElapsedTime = function() { - return this.elapsed_time; - }; - f.prototype.sendEventToAllNodes = function(a, b, e) { - e = e || l.ALWAYS; - var c = this._nodes_in_order ? this._nodes_in_order : this._nodes; - if (c) { - for (var h = 0, m = c.length; h < m; ++h) { - var p = c[h]; - if (p[a] && p.mode == e) { - if (void 0 === b) { - p[a](); - } else { - if (b && b.constructor === Array) { - p[a].apply(p, b); - } else { - p[a](b); - } - } - } - } - } - }; - f.prototype.sendActionToCanvas = function(a, b) { - if (this.list_of_graphcanvas) { - for (var e = 0; e < this.list_of_graphcanvas.length; ++e) { - var c = this.list_of_graphcanvas[e]; - c[a] && c[a].apply(c, b); - } - } - }; - f.prototype.add = function(a, b) { - if (a) { - if (a.constructor === n) { - this._groups.push(a), this.setDirtyCanvas(!0), this.change(), a.graph = this, this._version++; - } else { - -1 != a.id && null != this._nodes_by_id[a.id] && (console.warn("LiteGraph: there is already a node with this ID, changing it"), a.id = ++this.last_node_id); - if (this._nodes.length >= l.MAX_NUMBER_OF_NODES) { - throw "LiteGraph: max number of nodes in a graph reached"; - } - null == a.id || -1 == a.id ? a.id = ++this.last_node_id : this.last_node_id < a.id && (this.last_node_id = a.id); - a.graph = this; - this._version++; - this._nodes.push(a); - this._nodes_by_id[a.id] = a; - if (a.onAdded) { - a.onAdded(this); - } - this.config.align_to_grid && a.alignToGrid(); - b || this.updateExecutionOrder(); - if (this.onNodeAdded) { - this.onNodeAdded(a); - } - this.setDirtyCanvas(!0); - this.change(); - return a; - } - } - }; - f.prototype.remove = function(a) { - if (a.constructor === l.LGraphGroup) { - var b = this._groups.indexOf(a); - -1 != b && this._groups.splice(b, 1); - a.graph = null; - this._version++; - this.setDirtyCanvas(!0, !0); - this.change(); - } else { - if (null != this._nodes_by_id[a.id] && !a.ignore_remove) { - if (a.inputs) { - for (b = 0; b < a.inputs.length; b++) { - var e = a.inputs[b]; - null != e.link && a.disconnectInput(b); - } - } - if (a.outputs) { - for (b = 0; b < a.outputs.length; b++) { - e = a.outputs[b], null != e.links && e.links.length && a.disconnectOutput(b); - } - } - if (a.onRemoved) { - a.onRemoved(); - } - a.graph = null; - this._version++; - if (this.list_of_graphcanvas) { - for (b = 0; b < this.list_of_graphcanvas.length; ++b) { - e = this.list_of_graphcanvas[b], e.selected_nodes[a.id] && delete e.selected_nodes[a.id], e.node_dragged == a && (e.node_dragged = null); - } - } - b = this._nodes.indexOf(a); - -1 != b && this._nodes.splice(b, 1); - delete this._nodes_by_id[a.id]; - if (this.onNodeRemoved) { - this.onNodeRemoved(a); - } - this.setDirtyCanvas(!0, !0); - this.change(); - this.updateExecutionOrder(); - } - } - }; - f.prototype.getNodeById = function(a) { - return null == a ? null : this._nodes_by_id[a]; - }; - f.prototype.findNodesByClass = function(a) { - for (var b = [], e = 0, c = this._nodes.length; e < c; ++e) { - this._nodes[e].constructor === a && b.push(this._nodes[e]); - } - return b; - }; - f.prototype.findNodesByType = function(a) { - a = a.toLowerCase(); - for (var b = [], e = 0, c = this._nodes.length; e < c; ++e) { - this._nodes[e].type.toLowerCase() == a && b.push(this._nodes[e]); - } - return b; - }; - f.prototype.findNodesByTitle = function(a) { - for (var b = [], e = 0, c = this._nodes.length; e < c; ++e) { - this._nodes[e].title == a && b.push(this._nodes[e]); - } - return b; - }; - f.prototype.getNodeOnPos = function(a, b, e) { - e = e || this._nodes; - for (var c = e.length - 1; 0 <= c; c--) { - var h = e[c]; - if (h.isPointInside(a, b, 2)) { - return h; - } - } - return null; - }; - f.prototype.getGroupOnPos = function(a, b) { - for (var e = this._groups.length - 1; 0 <= e; e--) { - var c = this._groups[e]; - if (c.isPointInside(a, b, 2)) { - return c; - } - } - return null; - }; - f.prototype.addGlobalInput = function(a, b, e) { - this.global_inputs[a] = {name:a, type:b, value:e}; - this._version++; - if (this.onGlobalInputAdded) { - this.onGlobalInputAdded(a, b); - } - if (this.onGlobalsChange) { - this.onGlobalsChange(); - } - }; - f.prototype.setGlobalInputData = function(a, b) { - if (a = this.global_inputs[a]) { - a.value = b; - } - }; - f.prototype.setInputData = f.prototype.setGlobalInputData; - f.prototype.getGlobalInputData = function(a) { - return (a = this.global_inputs[a]) ? a.value : null; - }; - f.prototype.renameGlobalInput = function(a, b) { - if (b != a) { - if (!this.global_inputs[a]) { - return !1; - } - if (this.global_inputs[b]) { - return console.error("there is already one input with that name"), !1; - } - this.global_inputs[b] = this.global_inputs[a]; - delete this.global_inputs[a]; - this._version++; - if (this.onGlobalInputRenamed) { - this.onGlobalInputRenamed(a, b); - } - if (this.onGlobalsChange) { - this.onGlobalsChange(); - } - } - }; - f.prototype.changeGlobalInputType = function(a, b) { - if (!this.global_inputs[a]) { - return !1; - } - if (!this.global_inputs[a].type || this.global_inputs[a].type.toLowerCase() != b.toLowerCase()) { - if (this.global_inputs[a].type = b, this._version++, this.onGlobalInputTypeChanged) { - this.onGlobalInputTypeChanged(a, b); - } - } - }; - f.prototype.removeGlobalInput = function(a) { - if (!this.global_inputs[a]) { - return !1; - } - delete this.global_inputs[a]; - this._version++; - if (this.onGlobalInputRemoved) { - this.onGlobalInputRemoved(a); - } - if (this.onGlobalsChange) { - this.onGlobalsChange(); - } - return !0; - }; - f.prototype.addGlobalOutput = function(a, b, e) { - this.global_outputs[a] = {name:a, type:b, value:e}; - this._version++; - if (this.onGlobalOutputAdded) { - this.onGlobalOutputAdded(a, b); - } - if (this.onGlobalsChange) { - this.onGlobalsChange(); - } - }; - f.prototype.setGlobalOutputData = function(a, b) { - if (a = this.global_outputs[a]) { - a.value = b; - } - }; - f.prototype.getGlobalOutputData = function(a) { - return (a = this.global_outputs[a]) ? a.value : null; - }; - f.prototype.getOutputData = f.prototype.getGlobalOutputData; - f.prototype.renameGlobalOutput = function(a, b) { - if (!this.global_outputs[a]) { - return !1; - } - if (this.global_outputs[b]) { - return console.error("there is already one output with that name"), !1; - } - this.global_outputs[b] = this.global_outputs[a]; - delete this.global_outputs[a]; - this._version++; - if (this.onGlobalOutputRenamed) { - this.onGlobalOutputRenamed(a, b); - } - if (this.onGlobalsChange) { - this.onGlobalsChange(); - } - }; - f.prototype.changeGlobalOutputType = function(a, b) { - if (!this.global_outputs[a]) { - return !1; - } - if (!this.global_outputs[a].type || this.global_outputs[a].type.toLowerCase() != b.toLowerCase()) { - if (this.global_outputs[a].type = b, this._version++, this.onGlobalOutputTypeChanged) { - this.onGlobalOutputTypeChanged(a, b); - } - } - }; - f.prototype.removeGlobalOutput = function(a) { - if (!this.global_outputs[a]) { - return !1; - } - delete this.global_outputs[a]; - this._version++; - if (this.onGlobalOutputRemoved) { - this.onGlobalOutputRemoved(a); - } - if (this.onGlobalsChange) { - this.onGlobalsChange(); - } - return !0; - }; - f.prototype.triggerInput = function(a, b) { - a = this.findNodesByTitle(a); - for (var e = 0; e < a.length; ++e) { - a[e].onTrigger(b); - } - }; - f.prototype.setCallback = function(a, b) { - a = this.findNodesByTitle(a); - for (var e = 0; e < a.length; ++e) { - a[e].setTrigger(b); - } - }; - f.prototype.connectionChange = function(a) { - this.updateExecutionOrder(); - if (this.onConnectionChange) { - this.onConnectionChange(a); - } - this._version++; - this.sendActionToCanvas("onConnectionChange"); - }; - f.prototype.isLive = function() { - if (!this.list_of_graphcanvas) { - return !1; - } - for (var a = 0; a < this.list_of_graphcanvas.length; ++a) { - if (this.list_of_graphcanvas[a].live_mode) { - return !0; - } - } - return !1; - }; - f.prototype.change = function() { - l.debug && console.log("Graph changed"); - this.sendActionToCanvas("setDirty", [!0, !0]); - if (this.on_change) { - this.on_change(this); - } - }; - f.prototype.setDirtyCanvas = function(a, b) { - this.sendActionToCanvas("setDirty", [a, b]); - }; - f.prototype.serialize = function() { - for (var a = [], b = 0, e = this._nodes.length; b < e; ++b) { - a.push(this._nodes[b].serialize()); - } - e = []; - for (b in this.links) { - var c = this.links[b]; - e.push([c.id, c.origin_id, c.origin_slot, c.target_id, c.target_slot, c.type]); - } - c = []; - for (b = 0; b < this._groups.length; ++b) { - c.push(this._groups[b].serialize()); - } - return {last_node_id:this.last_node_id, last_link_id:this.last_link_id, nodes:a, links:e, groups:c, config:this.config}; - }; - f.prototype.configure = function(a, b) { - if (a) { - b || this.clear(); - b = a.nodes; - if (a.links && a.links.constructor === Array) { - for (var e = [], c = 0; c < a.links.length; ++c) { - var h = a.links[c]; - e[h[0]] = {id:h[0], origin_id:h[1], origin_slot:h[2], target_id:h[3], target_slot:h[4], type:h[5]}; - } - a.links = e; - } - for (c in a) { - this[c] = a[c]; - } - e = !1; - this._nodes = []; - if (b) { - c = 0; - for (h = b.length; c < h; ++c) { - var m = b[c], p = l.createNode(m.type, m.title); - p ? (p.id = m.id, this.add(p, !0)) : (l.debug && console.log("Node not found: " + m.type), e = !0); - } - c = 0; - for (h = b.length; c < h; ++c) { - m = b[c], (p = this.getNodeById(m.id)) && p.configure(m); - } - } - this._groups.length = 0; - if (a.groups) { - for (c = 0; c < a.groups.length; ++c) { - b = new l.LGraphGroup, b.configure(a.groups[c]), this.add(b); - } - } - this.updateExecutionOrder(); - this._version++; - this.setDirtyCanvas(!0, !0); - return e; - } - }; - f.prototype.load = function(a) { - var b = this, e = new XMLHttpRequest; - e.open("GET", a, !0); - e.send(null); - e.onload = function(a) { - 200 !== e.status ? console.error("Error loading graph:", e.status, e.response) : (a = JSON.parse(e.response), b.configure(a)); - }; - e.onerror = function(a) { - console.error("Error loading graph:", a); - }; - }; - f.prototype.onNodeTrace = function(a, b, e) { - }; - u.LGraphNode = l.LGraphNode = k; - k.prototype._ctor = function(a) { - this.title = a || "Unnamed"; - this.size = [l.NODE_WIDTH, 60]; - this.graph = null; - this._pos = new Float32Array(10, 10); - Object.defineProperty(this, "pos", {set:function(a) { - !a || 2 > a.length || (this._pos[0] = a[0], this._pos[1] = a[1]); - }, get:function() { - return this._pos; - }, enumerable:!0}); - this.id = -1; - this.type = null; - this.inputs = []; - this.outputs = []; - this.connections = []; - this.properties = {}; - this.properties_info = []; - this.data = null; - this.flags = {}; - }; - k.prototype.configure = function(a) { - this.graph && this.graph._version++; - for (var b in a) { - if ("console" != b) { - if ("properties" == b) { - for (var e in a.properties) { - if (this.properties[e] = a.properties[e], this.onPropertyChanged) { - this.onPropertyChanged(e, a.properties[e]); - } - } - } else { - null != a[b] && ("object" == typeof a[b] ? this[b] && this[b].configure ? this[b].configure(a[b]) : this[b] = l.cloneObject(a[b], this[b]) : this[b] = a[b]); - } - } - } - a.title || (this.title = this.constructor.title); - if (this.onConnectionsChange) { - if (this.inputs) { - for (var c = 0; c < this.inputs.length; ++c) { - e = this.inputs[c]; - var h = this.graph ? this.graph.links[e.link] : null; - this.onConnectionsChange(l.INPUT, c, !0, h, e); - } - } - if (this.outputs) { - for (c = 0; c < this.outputs.length; ++c) { - if (e = this.outputs[c], e.links) { - for (b = 0; b < e.links.length; ++b) { - h = this.graph ? this.graph.links[e.links[b]] : null, this.onConnectionsChange(l.OUTPUT, c, !0, h, e); - } - } - } - } - } - for (c in this.inputs) { - e = this.inputs[c], e.link && e.link.length && (h = e.link, "object" == typeof h && (e.link = h[0], this.graph && (this.graph.links[h[0]] = {id:h[0], origin_id:h[1], origin_slot:h[2], target_id:h[3], target_slot:h[4]}))); - } - for (c in this.outputs) { - if (e = this.outputs[c], e.links && 0 != e.links.length) { - for (b in e.links) { - h = e.links[b], "object" == typeof h && (e.links[b] = h[0]); - } - } - } - if (this.onConfigure) { - this.onConfigure(a); - } - }; - k.prototype.serialize = function() { - var a = {id:this.id, type:this.type, pos:this.pos, size:this.size, data:this.data, flags:l.cloneObject(this.flags), mode:this.mode}; - this.inputs && (a.inputs = this.inputs); - if (this.outputs) { - for (var b = 0; b < this.outputs.length; b++) { - delete this.outputs[b]._data; - } - a.outputs = this.outputs; - } - this.title && this.title != this.constructor.title && (a.title = this.title); - this.properties && (a.properties = l.cloneObject(this.properties)); - a.type || (a.type = this.constructor.type); - this.color && (a.color = this.color); - this.bgcolor && (a.bgcolor = this.bgcolor); - this.boxcolor && (a.boxcolor = this.boxcolor); - this.shape && (a.shape = this.shape); - if (this.onSerialize) { - this.onSerialize(a); - } - return a; - }; - k.prototype.clone = function() { - var a = l.createNode(this.type), b = l.cloneObject(this.serialize()); - if (b.inputs) { - for (var e = 0; e < b.inputs.length; ++e) { - b.inputs[e].link = null; - } - } - if (b.outputs) { - for (e = 0; e < b.outputs.length; ++e) { - b.outputs[e].links && (b.outputs[e].links.length = 0); - } - } - delete b.id; - a.configure(b); - return a; - }; - k.prototype.toString = function() { - return JSON.stringify(this.serialize()); - }; - k.prototype.getTitle = function() { - return this.title || this.constructor.title; - }; - k.prototype.setOutputData = function(a, b) { - if (this.outputs && !(-1 == a || a >= this.outputs.length)) { - var e = this.outputs[a]; - if (e && (e._data = b, this.outputs[a].links)) { - for (e = 0; e < this.outputs[a].links.length; e++) { - this.graph.links[this.outputs[a].links[e]].data = b; - } - } - } - }; - k.prototype.getInputData = function(a, b) { - if (this.inputs && !(a >= this.inputs.length || null == this.inputs[a].link)) { - a = this.graph.links[this.inputs[a].link]; - if (!a) { - return null; - } - if (!b) { - return a.data; - } - b = this.graph.getNodeById(a.origin_id); - if (!b) { - return a.data; - } - if (b.updateOutputData) { - b.updateOutputData(a.origin_slot); - } else { - if (b.onExecute) { - b.onExecute(); - } - } - return a.data; - } - }; - k.prototype.getInputDataByName = function(a, b) { - a = this.findInputSlot(a); - return -1 == a ? null : this.getInputData(a, b); - }; - k.prototype.isInputConnected = function(a) { - return this.inputs ? a < this.inputs.length && null != this.inputs[a].link : !1; - }; - k.prototype.getInputInfo = function(a) { - return this.inputs ? a < this.inputs.length ? this.inputs[a] : null : null; - }; - k.prototype.getInputNode = function(a) { - if (!this.inputs || a >= this.inputs.length) { - return null; - } - a = this.inputs[a]; - return a && null !== a.link ? (a = this.graph.links[a.link]) ? this.graph.getNodeById(a.origin_id) : null : null; - }; - k.prototype.getInputOrProperty = function(a) { - if (!this.inputs || !this.inputs.length) { - return this.properties ? this.properties[a] : null; - } - for (var b = 0, e = this.inputs.length; b < e; ++b) { - if (a == this.inputs[b].name) { - return (a = this.graph.links[this.inputs[b].link]) ? a.data : null; - } - } - return this.properties[a]; - }; - k.prototype.getOutputData = function(a) { - return !this.outputs || a >= this.outputs.length ? null : this.outputs[a]._data; - }; - k.prototype.getOutputInfo = function(a) { - return this.outputs ? a < this.outputs.length ? this.outputs[a] : null : null; - }; - k.prototype.isOutputConnected = function(a) { - return this.outputs ? a < this.outputs.length && this.outputs[a].links && this.outputs[a].links.length : !1; - }; - k.prototype.isAnyOutputConnected = function() { - if (!this.outputs) { - return !1; - } - for (var a = 0; a < this.outputs.length; ++a) { - if (this.outputs[a].links && this.outputs[a].links.length) { - return !0; - } - } - return !1; - }; - k.prototype.getOutputNodes = function(a) { - if (!this.outputs || 0 == this.outputs.length || a >= this.outputs.length) { - return null; - } - a = this.outputs[a]; - if (!a.links || 0 == a.links.length) { - return null; - } - for (var b = [], e = 0; e < a.links.length; e++) { - var c = this.graph.links[a.links[e]]; - c && (c = this.graph.getNodeById(c.target_id)) && b.push(c); - } - return b; - }; - k.prototype.trigger = function(a, b) { - if (this.outputs && this.outputs.length) { - this.graph && (this.graph._last_trigger_time = l.getTime()); - for (var e = 0; e < this.outputs.length; ++e) { - var c = this.outputs[e]; - !c || c.type !== l.EVENT || a && c.name != a || this.triggerSlot(e, b); - } - } - }; - k.prototype.triggerSlot = function(a, b) { - if (this.outputs && (a = this.outputs[a]) && (a = a.links) && a.length) { - this.graph && (this.graph._last_trigger_time = l.getTime()); - for (var e = 0; e < a.length; ++e) { - var c = this.graph.links[a[e]]; - if (c) { - var h = this.graph.getNodeById(c.target_id); - if (h) { - if (c._last_time = l.getTime(), c = h.inputs[c.target_slot], h.onAction) { - h.onAction(c.name, b); - } else { - if (h.mode === l.ON_TRIGGER && h.onExecute) { - h.onExecute(b); - } - } - } - } - } - } - }; - k.prototype.addProperty = function(a, b, e, c) { - e = {name:a, type:e, default_value:b}; - if (c) { - for (var h in c) { - e[h] = c[h]; - } - } - this.properties_info || (this.properties_info = []); - this.properties_info.push(e); - this.properties || (this.properties = {}); - this.properties[a] = b; - return e; - }; - k.prototype.addOutput = function(a, b, e) { - a = {name:a, type:b, links:null}; - if (e) { - for (var c in e) { - a[c] = e[c]; - } - } - this.outputs || (this.outputs = []); - this.outputs.push(a); - if (this.onOutputAdded) { - this.onOutputAdded(a); - } - this.size = this.computeSize(); - return a; - }; - k.prototype.addOutputs = function(a) { - for (var b = 0; b < a.length; ++b) { - var e = a[b], c = {name:e[0], type:e[1], link:null}; - if (a[2]) { - for (var h in e[2]) { - c[h] = e[2][h]; - } - } - this.outputs || (this.outputs = []); - this.outputs.push(c); - if (this.onOutputAdded) { - this.onOutputAdded(c); - } - } - this.size = this.computeSize(); - }; - k.prototype.removeOutput = function(a) { - this.disconnectOutput(a); - this.outputs.splice(a, 1); - this.size = this.computeSize(); - if (this.onOutputRemoved) { - this.onOutputRemoved(a); - } - }; - k.prototype.addInput = function(a, b, e) { - a = {name:a, type:b || 0, link:null}; - if (e) { - for (var c in e) { - a[c] = e[c]; - } - } - this.inputs || (this.inputs = []); - this.inputs.push(a); - this.size = this.computeSize(); - if (this.onInputAdded) { - this.onInputAdded(a); - } - return a; - }; - k.prototype.addInputs = function(a) { - for (var b = 0; b < a.length; ++b) { - var e = a[b], c = {name:e[0], type:e[1], link:null}; - if (a[2]) { - for (var h in e[2]) { - c[h] = e[2][h]; - } - } - this.inputs || (this.inputs = []); - this.inputs.push(c); - if (this.onInputAdded) { - this.onInputAdded(c); - } - } - this.size = this.computeSize(); - }; - k.prototype.removeInput = function(a) { - this.disconnectInput(a); - this.inputs.splice(a, 1); - this.size = this.computeSize(); - if (this.onInputRemoved) { - this.onInputRemoved(a); - } - }; - k.prototype.addConnection = function(a, b, e, c) { - a = {name:a, type:b, pos:e, direction:c, links:null}; - this.connections.push(a); - return a; - }; - k.prototype.computeSize = function(a, b) { - function e(a) { - return a ? c * a.length * 0.6 : 0; - } - a = Math.max(this.inputs ? this.inputs.length : 1, this.outputs ? this.outputs.length : 1); - b = b || new Float32Array([0, 0]); - a = Math.max(a, 1); - var c = l.NODE_TEXT_SIZE; - b[1] = (this.constructor.slot_start_y || 0) + a * (c + 1) + (this.widgets ? this.widgets.length : 0) * (l.NODE_WIDGET_HEIGHT + 4) + 4; - a = e(this.title); - var h = 0, m = 0; - if (this.inputs) { - for (var p = 0, g = this.inputs.length; p < g; ++p) { - var d = this.inputs[p]; - d = d.label || d.name || ""; - d = e(d); - h < d && (h = d); - } - } - if (this.outputs) { - for (p = 0, g = this.outputs.length; p < g; ++p) { - d = this.outputs[p], d = d.label || d.name || "", d = e(d), m < d && (m = d); - } - } - b[0] = Math.max(h + m + 10, a); - b[0] = Math.max(b[0], l.NODE_WIDTH); - if (this.onResize) { - this.onResize(b); - } - return b; - }; - k.prototype.addWidget = function(a, b, e, c, h) { - this.widgets || (this.widgets = []); - b = {type:a.toLowerCase(), name:b, value:e, callback:c, options:h || {}}; - void 0 !== b.options.y && (b.y = b.options.y); - if ("combo" == a && !b.options.values) { - throw "LiteGraph addWidget('combo',...) requires to pass values in options: { values:['red','blue'] }"; - } - this.widgets.push(b); - return b; - }; - k.prototype.getBounding = function(a) { - a = a || new Float32Array(4); - a[0] = this.pos[0] - 4; - a[1] = this.pos[1] - l.NODE_TITLE_HEIGHT; - a[2] = this.size[0] + 4; - a[3] = this.size[1] + l.NODE_TITLE_HEIGHT; - return a; - }; - k.prototype.isPointInside = function(a, b, e) { - e = e || 0; - var c = this.graph && this.graph.isLive() ? 0 : 20; - if (this.flags && this.flags.collapsed) { - if (r(a, b, this.pos[0] - e, this.pos[1] - l.NODE_TITLE_HEIGHT - e, (this._collapsed_width || l.NODE_COLLAPSED_WIDTH) + 2 * e, l.NODE_TITLE_HEIGHT + 2 * e)) { - return !0; - } - } else { - if (this.pos[0] - 4 - e < a && this.pos[0] + this.size[0] + 4 + e > a && this.pos[1] - c - e < b && this.pos[1] + this.size[1] + e > b) { - return !0; - } - } - return !1; - }; - k.prototype.getSlotInPosition = function(a, b) { - if (this.inputs) { - for (var e = 0, c = this.inputs.length; e < c; ++e) { - var h = this.inputs[e], m = this.getConnectionPos(!0, e); - if (r(a, b, m[0] - 10, m[1] - 5, 20, 10)) { - return {input:h, slot:e, link_pos:m, locked:h.locked}; - } - } - } - if (this.outputs) { - for (e = 0, c = this.outputs.length; e < c; ++e) { - if (h = this.outputs[e], m = this.getConnectionPos(!1, e), r(a, b, m[0] - 10, m[1] - 5, 20, 10)) { - return {output:h, slot:e, link_pos:m, locked:h.locked}; - } - } - } - return null; - }; - k.prototype.findInputSlot = function(a) { - if (!this.inputs) { - return -1; - } - for (var b = 0, e = this.inputs.length; b < e; ++b) { - if (a == this.inputs[b].name) { - return b; - } - } - return -1; - }; - k.prototype.findOutputSlot = function(a) { - if (!this.outputs) { - return -1; - } - for (var b = 0, e = this.outputs.length; b < e; ++b) { - if (a == this.outputs[b].name) { - return b; - } - } - return -1; - }; - k.prototype.connect = function(a, b, e) { - e = e || 0; - if (!this.graph) { - return console.log("Connect: Error, node doesnt belong to any graph. Nodes must be added first to a graph before connecting them."), !1; - } - if (a.constructor === String) { - if (a = this.findOutputSlot(a), -1 == a) { - return l.debug && console.log("Connect: Error, no slot of name " + a), !1; - } - } else { - if (!this.outputs || a >= this.outputs.length) { - return l.debug && console.log("Connect: Error, slot number not found"), !1; - } - } - b && b.constructor === Number && (b = this.graph.getNodeById(b)); - if (!b) { - throw "target node is null"; - } - if (b == this) { - return !1; - } - if (e.constructor === String) { - if (e = b.findInputSlot(e), -1 == e) { - return l.debug && console.log("Connect: Error, no slot of name " + e), !1; - } - } else { - if (e === l.EVENT) { - return !1; - } - if (!b.inputs || e >= b.inputs.length) { - return l.debug && console.log("Connect: Error, slot number not found"), !1; - } - } - null != b.inputs[e].link && b.disconnectInput(e); - this.setDirtyCanvas(!1, !0); - this.graph.connectionChange(this); - var c = this.outputs[a]; - if (b.onConnectInput && !1 === b.onConnectInput(e, c.type, c)) { - return !1; - } - var h = b.inputs[e]; - if (l.isValidConnection(c.type, h.type)) { - var m = {id:this.graph.last_link_id++, type:h.type, origin_id:this.id, origin_slot:a, target_id:b.id, target_slot:e}; - this.graph.links[m.id] = m; - null == c.links && (c.links = []); - c.links.push(m.id); - b.inputs[e].link = m.id; - this.graph && this.graph._version++; - if (this.onConnectionsChange) { - this.onConnectionsChange(l.OUTPUT, a, !0, m, c); - } - if (b.onConnectionsChange) { - b.onConnectionsChange(l.INPUT, e, !0, m, h); - } - } - this.setDirtyCanvas(!1, !0); - this.graph.connectionChange(this); - return !0; - }; - k.prototype.disconnectOutput = function(a, b) { - if (a.constructor === String) { - if (a = this.findOutputSlot(a), -1 == a) { - return l.debug && console.log("Connect: Error, no slot of name " + a), !1; - } - } else { - if (!this.outputs || a >= this.outputs.length) { - return l.debug && console.log("Connect: Error, slot number not found"), !1; - } - } - var e = this.outputs[a]; - if (!e.links || 0 == e.links.length) { - return !1; - } - if (b) { - b.constructor === Number && (b = this.graph.getNodeById(b)); - if (!b) { - throw "Target Node not found"; - } - for (var c = 0, h = e.links.length; c < h; c++) { - var m = e.links[c], p = this.graph.links[m]; - if (p.target_id == b.id) { - e.links.splice(c, 1); - var g = b.inputs[p.target_slot]; - g.link = null; - delete this.graph.links[m]; - this.graph && this.graph._version++; - if (b.onConnectionsChange) { - b.onConnectionsChange(l.INPUT, p.target_slot, !1, p, g); - } - if (this.onConnectionsChange) { - this.onConnectionsChange(l.OUTPUT, a, !1, p, e); - } - break; - } - } - } else { - c = 0; - for (h = e.links.length; c < h; c++) { - if (m = e.links[c], p = this.graph.links[m]) { - b = this.graph.getNodeById(p.target_id); - this.graph && this.graph._version++; - if (b && (g = b.inputs[p.target_slot], g.link = null, b.onConnectionsChange)) { - b.onConnectionsChange(l.INPUT, p.target_slot, !1, p, g); - } - delete this.graph.links[m]; - if (this.onConnectionsChange) { - this.onConnectionsChange(l.OUTPUT, a, !1, p, e); - } - } - } - e.links = null; - } - this.setDirtyCanvas(!1, !0); - this.graph.connectionChange(this); - return !0; - }; - k.prototype.disconnectInput = function(a) { - if (a.constructor === String) { - if (a = this.findInputSlot(a), -1 == a) { - return l.debug && console.log("Connect: Error, no slot of name " + a), !1; - } - } else { - if (!this.inputs || a >= this.inputs.length) { - return l.debug && console.log("Connect: Error, slot number not found"), !1; - } - } - var b = this.inputs[a]; - if (!b) { - return !1; - } - var e = this.inputs[a].link; - this.inputs[a].link = null; - var c = this.graph.links[e]; - if (c) { - var h = this.graph.getNodeById(c.origin_id); - if (!h) { - return !1; - } - var m = h.outputs[c.origin_slot]; - if (!m || !m.links || 0 == m.links.length) { - return !1; - } - for (var p = 0, g = m.links.length; p < g; p++) { - if (m.links[p] == e) { - m.links.splice(p, 1); - break; - } - } - delete this.graph.links[e]; - this.graph && this.graph._version++; - if (this.onConnectionsChange) { - this.onConnectionsChange(l.INPUT, a, !1, c, b); - } - if (h.onConnectionsChange) { - h.onConnectionsChange(l.OUTPUT, p, !1, c, m); - } - } - this.setDirtyCanvas(!1, !0); - this.graph.connectionChange(this); - return !0; - }; - k.prototype.getConnectionPos = function(a, b) { - return this.flags.collapsed ? a ? [this.pos[0], this.pos[1] - 0.5 * l.NODE_TITLE_HEIGHT] : [this.pos[0] + (this._collapsed_width || l.NODE_COLLAPSED_WIDTH), this.pos[1] - 0.5 * l.NODE_TITLE_HEIGHT] : a && -1 == b ? [this.pos[0] + 10, this.pos[1] + 10] : a && this.inputs.length > b && this.inputs[b].pos ? [this.pos[0] + this.inputs[b].pos[0], this.pos[1] + this.inputs[b].pos[1]] : !a && this.outputs.length > b && this.outputs[b].pos ? [this.pos[0] + this.outputs[b].pos[0], this.pos[1] + this.outputs[b].pos[1]] : - a ? [this.pos[0], this.pos[1] + 10 + b * l.NODE_SLOT_HEIGHT + (this.constructor.slot_start_y || 0)] : [this.pos[0] + this.size[0] + 1, this.pos[1] + 10 + b * l.NODE_SLOT_HEIGHT + (this.constructor.slot_start_y || 0)]; - }; - k.prototype.alignToGrid = function() { - this.pos[0] = l.CANVAS_GRID_SIZE * Math.round(this.pos[0] / l.CANVAS_GRID_SIZE); - this.pos[1] = l.CANVAS_GRID_SIZE * Math.round(this.pos[1] / l.CANVAS_GRID_SIZE); - }; - k.prototype.trace = function(a) { - this.console || (this.console = []); - this.console.push(a); - this.console.length > k.MAX_CONSOLE && this.console.shift(); - this.graph.onNodeTrace(this, a); - }; - k.prototype.setDirtyCanvas = function(a, b) { - this.graph && this.graph.sendActionToCanvas("setDirty", [a, b]); - }; - k.prototype.loadImage = function(a) { - var b = new Image; - b.src = l.node_images_path + a; - b.ready = !1; - var e = this; - b.onload = function() { - this.ready = !0; - e.setDirtyCanvas(!0); - }; - return b; - }; - k.prototype.captureInput = function(a) { - if (this.graph && this.graph.list_of_graphcanvas) { - for (var b = this.graph.list_of_graphcanvas, e = 0; e < b.length; ++e) { - var c = b[e]; - if (a || c.node_capturing_input == this) { - c.node_capturing_input = a ? this : null; - } - } - } - }; - k.prototype.collapse = function(a) { - this.graph._version++; - if (!1 !== this.constructor.collapsable || a) { - this.flags.collapsed = this.flags.collapsed ? !1 : !0, this.setDirtyCanvas(!0, !0); - } - }; - k.prototype.pin = function(a) { - this.graph._version++; - this.flags.pinned = void 0 === a ? !this.flags.pinned : a; - }; - k.prototype.localToScreen = function(a, b, e) { - return [(a + this.pos[0]) * e.scale + e.offset[0], (b + this.pos[1]) * e.scale + e.offset[1]]; - }; - u.LGraphGroup = l.LGraphGroup = n; - n.prototype._ctor = function(a) { - this.title = a || "Group"; - this._bounding = new Float32Array([10, 10, 140, 80]); - this._pos = this._bounding.subarray(0, 2); - this._size = this._bounding.subarray(2, 4); - this._nodes = []; - this.color = g.node_colors.pale_blue ? g.node_colors.pale_blue.groupcolor : "#AAA"; - this.graph = null; - Object.defineProperty(this, "pos", {set:function(a) { - !a || 2 > a.length || (this._pos[0] = a[0], this._pos[1] = a[1]); - }, get:function() { - return this._pos; - }, enumerable:!0}); - Object.defineProperty(this, "size", {set:function(a) { - !a || 2 > a.length || (this._size[0] = Math.max(140, a[0]), this._size[1] = Math.max(80, a[1])); - }, get:function() { - return this._size; - }, enumerable:!0}); - }; - n.prototype.configure = function(a) { - this.title = a.title; - this._bounding.set(a.bounding); - this.color = a.color; - }; - n.prototype.serialize = function() { - var a = this._bounding; - return {title:this.title, bounding:[a[0], a[1], a[2], a[3]], color:this.color}; - }; - n.prototype.move = function(a, b, e) { - this._pos[0] += a; - this._pos[1] += b; - if (!e) { - for (e = 0; e < this._nodes.length; ++e) { - var c = this._nodes[e]; - c.pos[0] += a; - c.pos[1] += b; - } - } - }; - n.prototype.recomputeInsideNodes = function() { - this._nodes.length = 0; - for (var a = this.graph._nodes, b = new Float32Array(4), e = 0; e < a.length; ++e) { - var c = a[e]; - c.getBounding(b); - v(this._bounding, b) && this._nodes.push(c); - } - }; - n.prototype.isPointInside = k.prototype.isPointInside; - n.prototype.setDirtyCanvas = k.prototype.setDirtyCanvas; - u.LGraphCanvas = l.LGraphCanvas = g; - g.link_type_colors = {"-1":"#F85", number:"#AAC", node:"#DCA"}; - g.gradients = {}; - g.prototype.clear = function() { - this.fps = this.render_time = this.last_draw_time = this.frame = 0; - this.scale = 1; - this.offset = [0, 0]; - this.dragging_rectangle = null; - this.selected_nodes = {}; - this.selected_group = null; - this.visible_nodes = []; - this.connecting_node = this.node_capturing_input = this.node_over = this.node_dragged = null; - this.highlighted_links = {}; - this.dirty_bgcanvas = this.dirty_canvas = !0; - this.node_widget = this.node_in_panel = this.dirty_area = null; - this.last_mouse = [0, 0]; - this.last_mouseclick = 0; - if (this.onClear) { - this.onClear(); - } - }; - g.prototype.setGraph = function(a, b) { - this.graph != a && (b || this.clear(), !a && this.graph ? this.graph.detachCanvas(this) : (a.attachCanvas(this), this.setDirty(!0, !0))); - }; - g.prototype.openSubgraph = function(a) { - if (!a) { - throw "graph cannot be null"; - } - if (this.graph == a) { - throw "graph cannot be the same"; - } - this.clear(); - this.graph && (this._graph_stack || (this._graph_stack = []), this._graph_stack.push(this.graph)); - a.attachCanvas(this); - this.setDirty(!0, !0); - }; - g.prototype.closeSubgraph = function() { - if (this._graph_stack && 0 != this._graph_stack.length) { - var a = this._graph_stack.pop(); - this.selected_nodes = {}; - this.highlighted_links = {}; - a.attachCanvas(this); - this.setDirty(!0, !0); - } - }; - g.prototype.setCanvas = function(a, b) { - if (a && a.constructor === String && (a = document.getElementById(a), !a)) { - throw "Error creating LiteGraph canvas: Canvas not found"; - } - if (a !== this.canvas && (!a && this.canvas && (b || this.unbindEvents()), this.canvas = a)) { - a.className += " lgraphcanvas"; - a.data = this; - this.bgcanvas = null; - this.bgcanvas || (this.bgcanvas = document.createElement("canvas"), this.bgcanvas.width = this.canvas.width, this.bgcanvas.height = this.canvas.height); - if (null == a.getContext) { - if ("canvas" != a.localName) { - throw "Element supplied for LGraphCanvas must be a element, you passed a " + a.localName; - } - throw "This browser doesnt support Canvas"; - } - null == (this.ctx = a.getContext("2d")) && (a.webgl_enabled || console.warn("This canvas seems to be WebGL, enabling WebGL renderer"), this.enableWebGL()); - this._mousemove_callback = this.processMouseMove.bind(this); - this._mouseup_callback = this.processMouseUp.bind(this); - b || this.bindEvents(); - } - }; - g.prototype._doNothing = function(a) { - a.preventDefault(); - return !1; - }; - g.prototype._doReturnTrue = function(a) { - a.preventDefault(); - return !0; - }; - g.prototype.bindEvents = function() { - if (this._events_binded) { - console.warn("LGraphCanvas: events already binded"); - } else { - var a = this.canvas, b = this.getCanvasWindow().document; - this._mousedown_callback = this.processMouseDown.bind(this); - this._mousewheel_callback = this.processMouseWheel.bind(this); - a.addEventListener("mousedown", this._mousedown_callback, !0); - a.addEventListener("mousemove", this._mousemove_callback); - a.addEventListener("mousewheel", this._mousewheel_callback, !1); - a.addEventListener("contextmenu", this._doNothing); - a.addEventListener("DOMMouseScroll", this._mousewheel_callback, !1); - a.addEventListener("touchstart", this.touchHandler, !0); - a.addEventListener("touchmove", this.touchHandler, !0); - a.addEventListener("touchend", this.touchHandler, !0); - a.addEventListener("touchcancel", this.touchHandler, !0); - this._key_callback = this.processKey.bind(this); - a.addEventListener("keydown", this._key_callback, !0); - b.addEventListener("keyup", this._key_callback, !0); - this._ondrop_callback = this.processDrop.bind(this); - a.addEventListener("dragover", this._doNothing, !1); - a.addEventListener("dragend", this._doNothing, !1); - a.addEventListener("drop", this._ondrop_callback, !1); - a.addEventListener("dragenter", this._doReturnTrue, !1); - this._events_binded = !0; - } - }; - g.prototype.unbindEvents = function() { - if (this._events_binded) { - var a = this.getCanvasWindow().document; - this.canvas.removeEventListener("mousedown", this._mousedown_callback); - this.canvas.removeEventListener("mousewheel", this._mousewheel_callback); - this.canvas.removeEventListener("DOMMouseScroll", this._mousewheel_callback); - this.canvas.removeEventListener("keydown", this._key_callback); - a.removeEventListener("keyup", this._key_callback); - this.canvas.removeEventListener("contextmenu", this._doNothing); - this.canvas.removeEventListener("drop", this._ondrop_callback); - this.canvas.removeEventListener("dragenter", this._doReturnTrue); - this.canvas.removeEventListener("touchstart", this.touchHandler); - this.canvas.removeEventListener("touchmove", this.touchHandler); - this.canvas.removeEventListener("touchend", this.touchHandler); - this.canvas.removeEventListener("touchcancel", this.touchHandler); - this._ondrop_callback = this._key_callback = this._mousewheel_callback = this._mousedown_callback = null; - this._events_binded = !1; - } else { - console.warn("LGraphCanvas: no events binded"); - } - }; - g.getFileExtension = function(a) { - var b = a.indexOf("?"); - -1 != b && (a = a.substr(0, b)); - b = a.lastIndexOf("."); - return -1 == b ? "" : a.substr(b + 1).toLowerCase(); - }; - g.prototype.enableWebGL = function() { - this.gl = this.ctx = enableWebGLCanvas(this.canvas); - this.ctx.webgl = !0; - this.bgcanvas = this.canvas; - this.bgctx = this.gl; - this.canvas.webgl_enabled = !0; - }; - g.prototype.setDirty = function(a, b) { - a && (this.dirty_canvas = !0); - b && (this.dirty_bgcanvas = !0); - }; - g.prototype.getCanvasWindow = function() { - if (!this.canvas) { - return window; - } - var a = this.canvas.ownerDocument; - return a.defaultView || a.parentWindow; - }; - g.prototype.startRendering = function() { - function a() { - this.pause_rendering || this.draw(); - var b = this.getCanvasWindow(); - this.is_rendering && b.requestAnimationFrame(a.bind(this)); - } - this.is_rendering || (this.is_rendering = !0, a.call(this)); - }; - g.prototype.stopRendering = function() { - this.is_rendering = !1; - }; - g.prototype.processMouseDown = function(a) { - if (this.graph) { - this.adjustMouseEvent(a); - var b = this.getCanvasWindow(); - g.active_canvas = this; - this.canvas.removeEventListener("mousemove", this._mousemove_callback); - b.document.addEventListener("mousemove", this._mousemove_callback, !0); - b.document.addEventListener("mouseup", this._mouseup_callback, !0); - var e = this.graph.getNodeOnPos(a.canvasX, a.canvasY, this.visible_nodes), c = !1, h = 300 > l.getTime() - this.last_mouseclick; - this.canvas_mouse[0] = a.canvasX; - this.canvas_mouse[1] = a.canvasY; - l.closeAllContextMenus(b); - if (1 == a.which) { - a.ctrlKey && (this.dragging_rectangle = new Float32Array(4), this.dragging_rectangle[0] = a.canvasX, this.dragging_rectangle[1] = a.canvasY, this.dragging_rectangle[2] = 1, this.dragging_rectangle[3] = 1, c = !0); - var m = !1; - if (e && this.allow_interaction && !c) { - this.live_mode || e.flags.pinned || this.bringToFront(e); - if (!this.connecting_node && !e.flags.collapsed && !this.live_mode) { - if (e.outputs) { - for (var p = 0, d = e.outputs.length; p < d; ++p) { - var f = e.outputs[p], n = e.getConnectionPos(!1, p); - if (r(a.canvasX, a.canvasY, n[0] - 10, n[1] - 5, 20, 10)) { - this.connecting_node = e; - this.connecting_output = f; - this.connecting_pos = e.getConnectionPos(!1, p); - this.connecting_slot = p; - if (h) { - if (e.onOutputDblClick) { - e.onOutputDblClick(p, a); - } - } else { - if (e.onOutputClick) { - e.onOutputClick(p, a); - } - } - c = !0; - break; - } - } - } - if (e.inputs) { - for (p = 0, d = e.inputs.length; p < d; ++p) { - if (f = e.inputs[p], n = e.getConnectionPos(!0, p), r(a.canvasX, a.canvasY, n[0] - 10, n[1] - 5, 20, 10)) { - if (h) { - if (e.onInputDblClick) { - e.onInputDblClick(p, a); - } - } else { - if (e.onInputClick) { - e.onInputClick(p, a); - } - } - null !== f.link && (e.disconnectInput(p), c = this.dirty_bgcanvas = !0); - } - } - } - !c && !1 !== e.flags.resizable && r(a.canvasX, a.canvasY, e.pos[0] + e.size[0] - 5, e.pos[1] + e.size[1] - 5, 5, 5) && (this.resizing_node = e, this.canvas.style.cursor = "se-resize", c = !0); - } - !c && r(a.canvasX, a.canvasY, e.pos[0], e.pos[1] - l.NODE_TITLE_HEIGHT, l.NODE_TITLE_HEIGHT, l.NODE_TITLE_HEIGHT) && (e.collapse(), c = !0); - if (!c) { - p = !1; - if (d = this.processNodeWidgets(e, this.canvas_mouse, a)) { - p = !0, this.node_widget = [e, d]; - } - if (h && this.selected_nodes[e.id]) { - if (e.onDblClick) { - e.onDblClick(a); - } - this.processNodeDblClicked(e); - p = !0; - } - e.onMouseDown && e.onMouseDown(a, [a.canvasX - e.pos[0], a.canvasY - e.pos[1]]) ? p = !0 : this.live_mode && (p = m = !0); - p || (this.allow_dragnodes && (this.node_dragged = e), this.selected_nodes[e.id] || this.processNodeSelected(e, a)); - this.dirty_canvas = !0; - } - } else { - this.selected_group = this.graph.getGroupOnPos(a.canvasX, a.canvasY), this.selected_group_resizing = !1, this.selected_group && (a.ctrlKey && (this.dragging_rectangle = null), 10 > q([a.canvasX, a.canvasY], [this.selected_group.pos[0] + this.selected_group.size[0], this.selected_group.pos[1] + this.selected_group.size[1]]) * this.scale ? this.selected_group_resizing = !0 : this.selected_group.recomputeInsideNodes()), h && this.showSearchBox(a), m = !0; - } - !c && m && this.allow_dragcanvas && (this.dragging_canvas = !0); - } else { - 2 != a.which && 3 == a.which && this.processContextMenu(e, a); - } - this.last_mouse[0] = a.localX; - this.last_mouse[1] = a.localY; - this.last_mouseclick = l.getTime(); - this.graph.change(); - (!b.document.activeElement || "input" != b.document.activeElement.nodeName.toLowerCase() && "textarea" != b.document.activeElement.nodeName.toLowerCase()) && a.preventDefault(); - a.stopPropagation(); - if (this.onMouseDown) { - this.onMouseDown(a); - } - return !1; - } - }; - g.prototype.processMouseMove = function(a) { - this.autoresize && this.resize(); - if (this.graph) { - g.active_canvas = this; - this.adjustMouseEvent(a); - var b = [a.localX, a.localY], c = [b[0] - this.last_mouse[0], b[1] - this.last_mouse[1]]; - this.last_mouse = b; - this.canvas_mouse[0] = a.canvasX; - this.canvas_mouse[1] = a.canvasY; - this.node_widget && (this.processNodeWidgets(this.node_widget[0], this.canvas_mouse, a, this.node_widget[1]), this.dirty_canvas = !0); - if (this.dragging_rectangle) { - this.dragging_rectangle[2] = a.canvasX - this.dragging_rectangle[0], this.dragging_rectangle[3] = a.canvasY - this.dragging_rectangle[1], this.dirty_canvas = !0; - } else { - if (this.selected_group) { - this.selected_group_resizing ? this.selected_group.size = [a.canvasX - this.selected_group.pos[0], a.canvasY - this.selected_group.pos[1]] : (this.selected_group.move(c[0] / this.scale, c[1] / this.scale, a.ctrlKey), this.selected_group._nodes.length && (this.dirty_canvas = !0)), this.dirty_bgcanvas = !0; - } else { - if (this.dragging_canvas) { - this.offset[0] += c[0] / this.scale, this.offset[1] += c[1] / this.scale, this.dirty_bgcanvas = this.dirty_canvas = !0; - } else { - if (this.allow_interaction) { - this.connecting_node && (this.dirty_canvas = !0); - var d = this.graph.getNodeOnPos(a.canvasX, a.canvasY, this.visible_nodes); - b = 0; - for (var h = this.graph._nodes.length; b < h; ++b) { - if (this.graph._nodes[b].mouseOver && d != this.graph._nodes[b]) { - this.graph._nodes[b].mouseOver = !1; - if (this.node_over && this.node_over.onMouseLeave) { - this.node_over.onMouseLeave(a); - } - this.node_over = null; - this.dirty_canvas = !0; - } - } - if (d) { - if (!d.mouseOver && (d.mouseOver = !0, this.node_over = d, this.dirty_canvas = !0, d.onMouseEnter)) { - d.onMouseEnter(a); - } - if (d.onMouseMove) { - d.onMouseMove(a); - } - if (this.connecting_node && (h = this._highlight_input || [0, 0], !this.isOverNodeBox(d, a.canvasX, a.canvasY))) { - var m = this.isOverNodeInput(d, a.canvasX, a.canvasY, h); - -1 != m && d.inputs[m] ? l.isValidConnection(this.connecting_output.type, d.inputs[m].type) && (this._highlight_input = h) : this._highlight_input = null; - } - this.canvas && (r(a.canvasX, a.canvasY, d.pos[0] + d.size[0] - 5, d.pos[1] + d.size[1] - 5, 5, 5) ? this.canvas.style.cursor = "se-resize" : this.canvas.style.cursor = null); - } else { - this.canvas && (this.canvas.style.cursor = null); - } - if (this.node_capturing_input && this.node_capturing_input != d && this.node_capturing_input.onMouseMove) { - this.node_capturing_input.onMouseMove(a); - } - if (this.node_dragged && !this.live_mode) { - for (b in this.selected_nodes) { - d = this.selected_nodes[b], d.pos[0] += c[0] / this.scale, d.pos[1] += c[1] / this.scale; - } - this.dirty_bgcanvas = this.dirty_canvas = !0; - } - this.resizing_node && !this.live_mode && (this.resizing_node.size[0] = a.canvasX - this.resizing_node.pos[0], this.resizing_node.size[1] = a.canvasY - this.resizing_node.pos[1], c = Math.max(this.resizing_node.inputs ? this.resizing_node.inputs.length : 0, this.resizing_node.outputs ? this.resizing_node.outputs.length : 0) * l.NODE_SLOT_HEIGHT + (this.resizing_node.widgets ? this.resizing_node.widgets.length : 0) * (l.NODE_WIDGET_HEIGHT + 4) + 4, this.resizing_node.size[1] < c && (this.resizing_node.size[1] = - c), this.resizing_node.size[0] < l.NODE_MIN_WIDTH && (this.resizing_node.size[0] = l.NODE_MIN_WIDTH), this.canvas.style.cursor = "se-resize", this.dirty_bgcanvas = this.dirty_canvas = !0); - } - } - } - } - a.preventDefault(); - return !1; - } - }; - g.prototype.processMouseUp = function(a) { - if (this.graph) { - var b = this.getCanvasWindow().document; - g.active_canvas = this; - b.removeEventListener("mousemove", this._mousemove_callback, !0); - this.canvas.addEventListener("mousemove", this._mousemove_callback, !0); - b.removeEventListener("mouseup", this._mouseup_callback, !0); - this.adjustMouseEvent(a); - b = l.getTime(); - a.click_time = b - this.last_mouseclick; - if (1 == a.which) { - if (this.selected_group = this.node_widget = null, this.selected_group_resizing = !1, this.dragging_rectangle) { - if (this.graph) { - var c = this.graph._nodes, d = new Float32Array(4); - this.deselectAllNodes(); - 0 > this.dragging_rectangle[2] && (this.dragging_rectangle[0] += this.dragging_rectangle[2]); - 0 > this.dragging_rectangle[3] && (this.dragging_rectangle[1] += this.dragging_rectangle[3]); - this.dragging_rectangle[2] = Math.abs(this.dragging_rectangle[2] * this.scale); - this.dragging_rectangle[3] = Math.abs(this.dragging_rectangle[3] * this.scale); - for (var h = 0; h < c.length; ++h) { - b = c[h], b.getBounding(d), v(this.dragging_rectangle, d) && this.selectNode(b, !0); - } - } - this.dragging_rectangle = null; - } else { - if (this.connecting_node) { - this.dirty_bgcanvas = this.dirty_canvas = !0; - if (b = this.graph.getNodeOnPos(a.canvasX, a.canvasY, this.visible_nodes)) { - this.connecting_output.type == l.EVENT && this.isOverNodeBox(b, a.canvasX, a.canvasY) ? this.connecting_node.connect(this.connecting_slot, b, l.EVENT) : (c = this.isOverNodeInput(b, a.canvasX, a.canvasY), -1 != c ? this.connecting_node.connect(this.connecting_slot, b, c) : (c = b.getInputInfo(0), this.connecting_output.type == l.EVENT ? this.connecting_node.connect(this.connecting_slot, b, l.EVENT) : c && !c.link && l.isValidConnection(c.type && this.connecting_output.type) && this.connecting_node.connect(this.connecting_slot, - b, 0))); - } - this.connecting_node = this.connecting_pos = this.connecting_output = null; - this.connecting_slot = -1; - } else { - if (this.resizing_node) { - this.dirty_bgcanvas = this.dirty_canvas = !0, this.resizing_node = null; - } else { - if (this.node_dragged) { - this.dirty_bgcanvas = this.dirty_canvas = !0, this.node_dragged.pos[0] = Math.round(this.node_dragged.pos[0]), this.node_dragged.pos[1] = Math.round(this.node_dragged.pos[1]), this.graph.config.align_to_grid && this.node_dragged.alignToGrid(), this.node_dragged = null; - } else { - b = this.graph.getNodeOnPos(a.canvasX, a.canvasY, this.visible_nodes); - !b && 300 > a.click_time && this.deselectAllNodes(); - this.dirty_canvas = !0; - this.dragging_canvas = !1; - if (this.node_over && this.node_over.onMouseUp) { - this.node_over.onMouseUp(a, [a.canvasX - this.node_over.pos[0], a.canvasY - this.node_over.pos[1]]); - } - if (this.node_capturing_input && this.node_capturing_input.onMouseUp) { - this.node_capturing_input.onMouseUp(a, [a.canvasX - this.node_capturing_input.pos[0], a.canvasY - this.node_capturing_input.pos[1]]); - } - } - } - } - } - } else { - 2 == a.which ? (this.dirty_canvas = !0, this.dragging_canvas = !1) : 3 == a.which && (this.dirty_canvas = !0, this.dragging_canvas = !1); - } - this.graph.change(); - a.stopPropagation(); - a.preventDefault(); - return !1; - } - }; - g.prototype.processMouseWheel = function(a) { - if (this.graph && this.allow_dragcanvas) { - var b = null != a.wheelDeltaY ? a.wheelDeltaY : -60 * a.detail; - this.adjustMouseEvent(a); - var c = this.scale; - 0 < b ? c *= 1.1 : 0 > b && (c *= 1 / 1.1); - this.setZoom(c, [a.localX, a.localY]); - this.graph.change(); - a.preventDefault(); - return !1; - } - }; - g.prototype.isOverNodeBox = function(a, b, c) { - var e = l.NODE_TITLE_HEIGHT; - return r(b, c, a.pos[0] + 2, a.pos[1] + 2 - e, e - 4, e - 4) ? !0 : !1; - }; - g.prototype.isOverNodeInput = function(a, b, c, g) { - if (a.inputs) { - for (var e = 0, m = a.inputs.length; e < m; ++e) { - var p = a.getConnectionPos(!0, e); - if (r(b, c, p[0] - 10, p[1] - 5, 20, 10)) { - return g && (g[0] = p[0], g[1] = p[1]), e; - } - } - } - return -1; - }; - g.prototype.processKey = function(a) { - if (this.graph) { - var b = !1; - if ("input" != a.target.localName) { - if ("keydown" == a.type) { - 32 == a.keyCode && (b = this.dragging_canvas = !0); - 65 == a.keyCode && a.ctrlKey && (this.selectNodes(), b = !0); - "KeyC" == a.code && (a.metaKey || a.ctrlKey) && !a.shiftKey && this.selected_nodes && (this.copyToClipboard(), b = !0); - "KeyV" != a.code || !a.metaKey && !a.ctrlKey || a.shiftKey || this.pasteFromClipboard(); - if (46 == a.keyCode || 8 == a.keyCode) { - this.deleteSelectedNodes(), b = !0; - } - if (this.selected_nodes) { - for (var c in this.selected_nodes) { - if (this.selected_nodes[c].onKeyDown) { - this.selected_nodes[c].onKeyDown(a); - } - } - } - } else { - if ("keyup" == a.type && (32 == a.keyCode && (this.dragging_canvas = !1), this.selected_nodes)) { - for (c in this.selected_nodes) { - if (this.selected_nodes[c].onKeyUp) { - this.selected_nodes[c].onKeyUp(a); - } - } - } - } - this.graph.change(); - if (b) { - return a.preventDefault(), !1; - } - } - } - }; - g.prototype.copyToClipboard = function() { - var a = {nodes:[], links:[]}, b = 0, c = [], g; - for (g in this.selected_nodes) { - var h = this.selected_nodes[g]; - h._relative_id = b; - c.push(h); - b += 1; - } - for (g = 0; g < c.length; ++g) { - if (h = c[g], a.nodes.push(h.clone().serialize()), h.inputs && h.inputs.length) { - for (b = 0; b < h.inputs.length; ++b) { - var m = h.inputs[b]; - if (m && null != m.link && (m = this.graph.links[m.link])) { - var p = this.graph.getNodeById(m.origin_id); - p && this.selected_nodes[p.id] && a.links.push([p._relative_id, b, h._relative_id, m.target_slot]); - } - } - } - } - localStorage.setItem("litegrapheditor_clipboard", JSON.stringify(a)); - }; - g.prototype.pasteFromClipboard = function() { - var a = localStorage.getItem("litegrapheditor_clipboard"); - if (a) { - a = JSON.parse(a); - for (var b = [], c = 0; c < a.nodes.length; ++c) { - var g = a.nodes[c], h = l.createNode(g.type); - h && (h.configure(g), h.pos[0] += 5, h.pos[1] += 5, this.graph.add(h), b.push(h)); - } - for (c = 0; c < a.links.length; ++c) { - g = a.links[c], b[g[0]].connect(g[1], b[g[2]], g[3]); - } - this.selectNodes(b); - } - }; - g.prototype.processDrop = function(a) { - a.preventDefault(); - this.adjustMouseEvent(a); - var b = [a.canvasX, a.canvasY], c = this.graph.getNodeOnPos(b[0], b[1]); - if (c) { - if ((c.onDropFile || c.onDropData) && (b = a.dataTransfer.files) && b.length) { - for (var d = 0; d < b.length; d++) { - var h = a.dataTransfer.files[0], m = h.name; - g.getFileExtension(m); - if (c.onDropFile) { - c.onDropFile(h); - } - if (c.onDropData) { - var p = new FileReader; - p.onload = function(a) { - c.onDropData(a.target.result, m, h); - }; - var l = h.type.split("/")[0]; - "text" == l || "" == l ? p.readAsText(h) : "image" == l ? p.readAsDataURL(h) : p.readAsArrayBuffer(h); - } - } - } - return c.onDropItem && c.onDropItem(event) ? !0 : this.onDropItem ? this.onDropItem(event) : !1; - } - b = null; - this.onDropItem && (b = this.onDropItem(event)); - b || this.checkDropItem(a); - }; - g.prototype.checkDropItem = function(a) { - if (a.dataTransfer.files.length) { - var b = a.dataTransfer.files[0], c = g.getFileExtension(b.name).toLowerCase(); - if (c = l.node_types_by_file_extension[c]) { - if (c = l.createNode(c.type), c.pos = [a.canvasX, a.canvasY], this.graph.add(c), c.onDropFile) { - c.onDropFile(b); - } - } - } - }; - g.prototype.processNodeDblClicked = function(a) { - if (this.onShowNodePanel) { - this.onShowNodePanel(a); - } - if (this.onNodeDblClicked) { - this.onNodeDblClicked(a); - } - this.setDirty(!0); - }; - g.prototype.processNodeSelected = function(a, b) { - this.selectNode(a, b && b.shiftKey); - if (this.onNodeSelected) { - this.onNodeSelected(a); - } - }; - g.prototype.processNodeDeselected = function(a) { - this.deselectNode(a); - if (this.onNodeDeselected) { - this.onNodeDeselected(a); - } - }; - g.prototype.selectNode = function(a, b) { - null == a ? this.deselectAllNodes() : this.selectNodes([a], b); - }; - g.prototype.selectNodes = function(a, b) { - b || this.deselectAllNodes(); - a = a || this.graph._nodes; - for (b = 0; b < a.length; ++b) { - var c = a[b]; - if (!c.selected) { - if (!c.selected && c.onSelected) { - c.onSelected(); - } - c.selected = !0; - this.selected_nodes[c.id] = c; - if (c.inputs) { - for (b = 0; b < c.inputs.length; ++b) { - this.highlighted_links[c.inputs[b].link] = !0; - } - } - if (c.outputs) { - for (b = 0; b < c.outputs.length; ++b) { - var g = c.outputs[b]; - if (g.links) { - for (var h = 0; h < g.links.length; ++h) { - this.highlighted_links[g.links[h]] = !0; - } - } - } - } - } - } - this.setDirty(!0); - }; - g.prototype.deselectNode = function(a) { - if (a.selected) { - if (a.onDeselected) { - a.onDeselected(); - } - a.selected = !1; - if (a.inputs) { - for (var b = 0; b < a.inputs.length; ++b) { - delete this.highlighted_links[a.inputs[b].link]; - } - } - if (a.outputs) { - for (b = 0; b < a.outputs.length; ++b) { - var c = a.outputs[b]; - if (c.links) { - for (var g = 0; g < c.links.length; ++g) { - delete this.highlighted_links[c.links[g]]; - } - } - } - } - } - }; - g.prototype.deselectAllNodes = function() { - if (this.graph) { - for (var a = this.graph._nodes, b = 0, c = a.length; b < c; ++b) { - var g = a[b]; - if (g.selected) { - if (g.onDeselected) { - g.onDeselected(); - } - g.selected = !1; - } - } - this.selected_nodes = {}; - this.highlighted_links = {}; - this.setDirty(!0); - } - }; - g.prototype.deleteSelectedNodes = function() { - for (var a in this.selected_nodes) { - this.graph.remove(this.selected_nodes[a]); - } - this.selected_nodes = {}; - this.highlighted_links = {}; - this.setDirty(!0); - }; - g.prototype.centerOnNode = function(a) { - this.offset[0] = -a.pos[0] - 0.5 * a.size[0] + 0.5 * this.canvas.width / this.scale; - this.offset[1] = -a.pos[1] - 0.5 * a.size[1] + 0.5 * this.canvas.height / this.scale; - this.setDirty(!0, !0); - }; - g.prototype.adjustMouseEvent = function(a) { - if (this.canvas) { - var b = this.canvas.getBoundingClientRect(); - a.localX = a.pageX - b.left; - a.localY = a.pageY - b.top; - } else { - a.localX = a.pageX, a.localY = a.pageY; - } - a.deltaX = a.localX - this.last_mouse_position[0]; - a.deltaY = a.localY - this.last_mouse_position[1]; - this.last_mouse_position[0] = a.localX; - this.last_mouse_position[1] = a.localY; - a.canvasX = a.localX / this.scale - this.offset[0]; - a.canvasY = a.localY / this.scale - this.offset[1]; - }; - g.prototype.setZoom = function(a, b) { - !b && this.canvas && (b = [0.5 * this.canvas.width, 0.5 * this.canvas.height]); - var c = this.convertOffsetToCanvas(b); - this.scale = a; - this.scale > this.max_zoom ? this.scale = this.max_zoom : this.scale < this.min_zoom && (this.scale = this.min_zoom); - a = this.convertOffsetToCanvas(b); - c = [a[0] - c[0], a[1] - c[1]]; - this.offset[0] += c[0]; - this.offset[1] += c[1]; - this.dirty_bgcanvas = this.dirty_canvas = !0; - }; - g.prototype.convertOffsetToCanvas = function(a, b) { - b = b || []; - b[0] = a[0] / this.scale - this.offset[0]; - b[1] = a[1] / this.scale - this.offset[1]; - return b; - }; - g.prototype.convertCanvasToOffset = function(a, b) { - b = b || []; - b[0] = (a[0] + this.offset[0]) * this.scale; - b[1] = (a[1] + this.offset[1]) * this.scale; - return b; - }; - g.prototype.convertEventToCanvas = function(a) { - var b = this.canvas.getBoundingClientRect(); - return this.convertOffsetToCanvas([a.pageX - b.left, a.pageY - b.top]); - }; - g.prototype.bringToFront = function(a) { - var b = this.graph._nodes.indexOf(a); - -1 != b && (this.graph._nodes.splice(b, 1), this.graph._nodes.push(a)); - }; - g.prototype.sendToBack = function(a) { - var b = this.graph._nodes.indexOf(a); - -1 != b && (this.graph._nodes.splice(b, 1), this.graph._nodes.unshift(a)); - }; - var d = new Float32Array(4); - g.prototype.computeVisibleNodes = function(a, b) { - b = b || []; - b.length = 0; - a = a || this.graph._nodes; - for (var c = 0, g = a.length; c < g; ++c) { - var h = a[c]; - (!this.live_mode || h.onDrawBackground || h.onDrawForeground) && v(this.visible_area, h.getBounding(d)) && b.push(h); - } - return b; - }; - g.prototype.draw = function(a, b) { - if (this.canvas) { - var c = l.getTime(); - this.render_time = 0.001 * (c - this.last_draw_time); - this.last_draw_time = c; - if (this.graph) { - var g = [-this.offset[0], -this.offset[1]], h = [g[0] + this.canvas.width / this.scale, g[1] + this.canvas.height / this.scale]; - this.visible_area = new Float32Array([g[0], g[1], h[0] - g[0], h[1] - g[1]]); - } - (this.dirty_bgcanvas || b || this.always_render_background || this.graph && this.graph._last_trigger_time && 1000 > c - this.graph._last_trigger_time) && this.drawBackCanvas(); - (this.dirty_canvas || a) && this.drawFrontCanvas(); - this.fps = this.render_time ? 1.0 / this.render_time : 0; - this.frame += 1; - } - }; - g.prototype.drawFrontCanvas = function() { - this.dirty_canvas = !1; - this.ctx || (this.ctx = this.bgcanvas.getContext("2d")); - var a = this.ctx; - if (a) { - a.start2D && a.start2D(); - var b = this.canvas; - a.restore(); - a.setTransform(1, 0, 0, 1, 0, 0); - this.dirty_area && (a.save(), a.beginPath(), a.rect(this.dirty_area[0], this.dirty_area[1], this.dirty_area[2], this.dirty_area[3]), a.clip()); - this.clear_background && a.clearRect(0, 0, b.width, b.height); - this.bgcanvas == this.canvas ? this.drawBackCanvas() : a.drawImage(this.bgcanvas, 0, 0); - if (this.onRender) { - this.onRender(b, a); - } - this.show_info && this.renderInfo(a); - if (this.graph) { - a.save(); - a.scale(this.scale, this.scale); - a.translate(this.offset[0], this.offset[1]); - b = this.computeVisibleNodes(null, this.visible_nodes); - for (var c = 0; c < b.length; ++c) { - var g = b[c]; - a.save(); - a.translate(g.pos[0], g.pos[1]); - this.drawNode(g, a); - a.restore(); - } - this.graph.config.links_ontop && (this.live_mode || this.drawConnections(a)); - if (null != this.connecting_pos) { - a.lineWidth = this.connections_width; - switch(this.connecting_output.type) { - case l.EVENT: - b = "#F85"; - break; - default: - b = "#AFA"; - } - this.renderLink(a, this.connecting_pos, [this.canvas_mouse[0], this.canvas_mouse[1]], null, !1, null, b); - a.beginPath(); - this.connecting_output.type === l.EVENT || this.connecting_output.shape === l.BOX_SHAPE ? a.rect(this.connecting_pos[0] - 6 + 0.5, this.connecting_pos[1] - 5 + 0.5, 14, 10) : a.arc(this.connecting_pos[0], this.connecting_pos[1], 4, 0, 2 * Math.PI); - a.fill(); - a.fillStyle = "#ffcc00"; - this._highlight_input && (a.beginPath(), a.arc(this._highlight_input[0], this._highlight_input[1], 6, 0, 2 * Math.PI), a.fill()); - } - this.dragging_rectangle && (a.strokeStyle = "#FFF", a.strokeRect(this.dragging_rectangle[0], this.dragging_rectangle[1], this.dragging_rectangle[2], this.dragging_rectangle[3])); - a.restore(); - } - this.dirty_area && a.restore(); - a.finish2D && a.finish2D(); - } - }; - g.prototype.renderInfo = function(a, b, c) { - b = b || 0; - c = c || 0; - a.save(); - a.translate(b, c); - a.font = "10px Arial"; - a.fillStyle = "#888"; - this.graph ? (a.fillText("T: " + this.graph.globaltime.toFixed(2) + "s", 5, 13), a.fillText("I: " + this.graph.iteration, 5, 26), a.fillText("V: " + this.graph._version, 5, 39), a.fillText("FPS:" + this.fps.toFixed(2), 5, 52)) : a.fillText("No graph selected", 5, 13); - a.restore(); - }; - g.prototype.drawBackCanvas = function() { - var a = this.bgcanvas; - if (a.width != this.canvas.width || a.height != this.canvas.height) { - a.width = this.canvas.width, a.height = this.canvas.height; - } - this.bgctx || (this.bgctx = this.bgcanvas.getContext("2d")); - var b = this.bgctx; - b.start && b.start(); - this.clear_background && b.clearRect(0, 0, a.width, a.height); - this._graph_stack && this._graph_stack.length && (b.strokeStyle = this._graph_stack[this._graph_stack.length - 1].bgcolor, b.lineWidth = 10, b.strokeRect(1, 1, a.width - 2, a.height - 2), b.lineWidth = 1); - var c = !1; - this.onRenderBackground && (c = this.onRenderBackground()); - b.restore(); - b.setTransform(1, 0, 0, 1, 0, 0); - if (this.graph) { - b.save(); - b.scale(this.scale, this.scale); - b.translate(this.offset[0], this.offset[1]); - if (this.background_image && 0.5 < this.scale && !c) { - b.globalAlpha = this.zoom_modify_alpha ? (1.0 - 0.5 / this.scale) * this.editor_alpha : this.editor_alpha; - b.imageSmoothingEnabled = b.mozImageSmoothingEnabled = b.imageSmoothingEnabled = !1; - if (!this._bg_img || this._bg_img.name != this.background_image) { - this._bg_img = new Image; - this._bg_img.name = this.background_image; - this._bg_img.src = this.background_image; - var g = this; - this._bg_img.onload = function() { - g.draw(!0, !0); - }; - } - c = null; - null == this._pattern && 0 < this._bg_img.width ? (c = b.createPattern(this._bg_img, "repeat"), this._pattern_img = this._bg_img, this._pattern = c) : c = this._pattern; - c && (b.fillStyle = c, b.fillRect(this.visible_area[0], this.visible_area[1], this.visible_area[2], this.visible_area[3]), b.fillStyle = "transparent"); - b.globalAlpha = 1.0; - b.imageSmoothingEnabled = b.mozImageSmoothingEnabled = b.imageSmoothingEnabled = !0; - } - this.graph._groups.length && !this.live_mode && this.drawGroups(a, b); - if (this.onBackgroundRender) { - this.onBackgroundRender(a, b); - } - this.render_canvas_border && (b.strokeStyle = "#235", b.strokeRect(0, 0, a.width, a.height)); - this.render_connections_shadows ? (b.shadowColor = "#000", b.shadowOffsetX = 0, b.shadowOffsetY = 0, b.shadowBlur = 6) : b.shadowColor = "rgba(0,0,0,0)"; - this.live_mode || this.drawConnections(b); - b.shadowColor = "rgba(0,0,0,0)"; - b.restore(); - } - b.finish && b.finish(); - this.dirty_bgcanvas = !1; - this.dirty_canvas = !0; - }; - var c = new Float32Array(2); - g.prototype.drawNode = function(a, b) { - this.current_node = a; - var e = a.color || a.constructor.color || l.NODE_DEFAULT_COLOR, g = a.bgcolor || a.constructor.bgcolor || l.NODE_DEFAULT_BGCOLOR; - a.selected || (this.render_shadows ? (b.shadowColor = "rgba(0,0,0,0.5)", b.shadowOffsetX = 2, b.shadowOffsetY = 2, b.shadowBlur = 3) : b.shadowColor = "transparent"); - if (this.live_mode) { - if (!a.flags.collapsed && (b.shadowColor = "transparent", a.onDrawForeground)) { - a.onDrawForeground(b, this); - } - } else { - if (!a.flags.collapsed || !a.onDrawCollaped || 1 != a.onDrawCollapsed(b, this)) { - var h = this.editor_alpha; - b.globalAlpha = h; - var m = a._shape || l.BOX_SHAPE; - c.set(a.size); - if (a.flags.collapsed) { - b.font = this.inner_text_font; - var p = a.getTitle ? a.getTitle() : a.title; - a._collapsed_width = Math.min(a.size[0], b.measureText(p).width + 40); - c[0] = a._collapsed_width; - c[1] = 0; - } - a.flags.clip_area && (b.save(), b.beginPath(), m == l.BOX_SHAPE ? b.rect(0, 0, c[0], c[1]) : m == l.ROUND_SHAPE ? b.roundRect(0, 0, c[0], c[1], 10) : m == l.CIRCLE_SHAPE && b.arc(0.5 * c[0], 0.5 * c[1], 0.5 * c[0], 0, 2 * Math.PI), b.clip()); - this.drawNodeShape(a, b, c, e, g, a.selected, a.mouseOver); - b.shadowColor = "transparent"; - b.textAlign = "left"; - b.font = this.inner_text_font; - e = 0.6 < this.scale; - g = this.connecting_output; - b.lineWidth = 1; - m = 0; - if (a.flags.collapsed) { - if (a.inputs) { - for (p = 0; p < a.inputs.length; p++) { - if (d = a.inputs[p], null != d.link) { - b.fillStyle = d.colorOn || this.default_connection_color.input_on; - b.beginPath(); - d.type === l.EVENT || d.shape === l.BOX_SHAPE ? b.rect(0.5, 4 - l.NODE_TITLE_HEIGHT + 0.5, 14, l.NODE_TITLE_HEIGHT - 8) : d.shape === l.ARROW_SHAPE ? (b.moveTo(8, -0.5 * l.NODE_TITLE_HEIGHT), b.lineTo(-4, -0.8 * l.NODE_TITLE_HEIGHT), b.lineTo(-4, -0.2 * l.NODE_TITLE_HEIGHT), b.closePath()) : b.arc(0, -0.5 * l.NODE_TITLE_HEIGHT, 4, 0, 2 * Math.PI); - b.fill(); - break; - } - } - } - if (a.outputs) { - for (p = 0; p < a.outputs.length; p++) { - d = a.outputs[p], d.links && d.links.length && (b.fillStyle = d.colorOn || this.default_connection_color.output_on, b.strokeStyle = "black", b.beginPath(), d.type === l.EVENT || d.shape === l.BOX_SHAPE ? b.rect(a._collapsed_width - 4 + 0.5, 4 - l.NODE_TITLE_HEIGHT + 0.5, 14, l.NODE_TITLE_HEIGHT - 8) : d.shape === l.ARROW_SHAPE ? (b.moveTo(a._collapsed_width + 6, -0.5 * l.NODE_TITLE_HEIGHT), b.lineTo(a._collapsed_width - 6, -0.8 * l.NODE_TITLE_HEIGHT), b.lineTo(a._collapsed_width - 6, - -0.2 * l.NODE_TITLE_HEIGHT), b.closePath()) : b.arc(a._collapsed_width, -0.5 * l.NODE_TITLE_HEIGHT, 4, 0, 2 * Math.PI), b.fill(), b.stroke()); - } - } - } else { - if (a.inputs) { - for (p = 0; p < a.inputs.length; p++) { - var d = a.inputs[p]; - b.globalAlpha = h; - this.connecting_node && l.isValidConnection(d.type && g.type) && (b.globalAlpha = 0.4 * h); - b.fillStyle = null != d.link ? d.colorOn || this.default_connection_color.input_on : d.colorOff || this.default_connection_color.input_off; - var f = a.getConnectionPos(!0, p); - f[0] -= a.pos[0]; - f[1] -= a.pos[1]; - m < f[1] + 0.5 * l.NODE_SLOT_HEIGHT && (m = f[1] + 0.5 * l.NODE_SLOT_HEIGHT); - b.beginPath(); - d.type === l.EVENT || d.shape === l.BOX_SHAPE ? b.rect(f[0] - 6 + 0.5, f[1] - 5 + 0.5, 14, 10) : d.shape === l.ARROW_SHAPE ? (b.moveTo(f[0] + 8, f[1] + 0.5), b.lineTo(f[0] - 4, f[1] + 6 + 0.5), b.lineTo(f[0] - 4, f[1] - 6 + 0.5), b.closePath()) : b.arc(f[0], f[1], 4, 0, 2 * Math.PI); - b.fill(); - e && (d = null != d.label ? d.label : d.name) && (b.fillStyle = l.NODE_TEXT_COLOR, b.fillText(d, f[0] + 10, f[1] + 5)); - } - } - this.connecting_node && (b.globalAlpha = 0.4 * h); - b.textAlign = "right"; - b.strokeStyle = "black"; - if (a.outputs) { - for (p = 0; p < a.outputs.length; p++) { - if (d = a.outputs[p], f = a.getConnectionPos(!1, p), f[0] -= a.pos[0], f[1] -= a.pos[1], m < f[1] + 0.5 * l.NODE_SLOT_HEIGHT && (m = f[1] + 0.5 * l.NODE_SLOT_HEIGHT), b.fillStyle = d.links && d.links.length ? d.colorOn || this.default_connection_color.output_on : d.colorOff || this.default_connection_color.output_off, b.beginPath(), d.type === l.EVENT || d.shape === l.BOX_SHAPE ? b.rect(f[0] - 6 + 0.5, f[1] - 5 + 0.5, 14, 10) : d.shape === l.ARROW_SHAPE ? (b.moveTo(f[0] + 8, f[1] + - 0.5), b.lineTo(f[0] - 4, f[1] + 6 + 0.5), b.lineTo(f[0] - 4, f[1] - 6 + 0.5), b.closePath()) : b.arc(f[0], f[1], 4, 0, 2 * Math.PI), b.fill(), b.stroke(), e && (d = null != d.label ? d.label : d.name)) { - b.fillStyle = l.NODE_TEXT_COLOR, b.fillText(d, f[0] - 10, f[1] + 5); - } - } - } - b.textAlign = "left"; - b.globalAlpha = 1; - a.widgets && this.drawNodeWidgets(a, m, b, this.node_widget && this.node_widget[0] == a ? this.node_widget[1] : null); - a.onDrawForeground && (a.gui_rects && (a.gui_rects.length = 0), a.onDrawForeground(b, this)); - } - a.flags.clip_area && b.restore(); - b.globalAlpha = 1.0; - } - } - }; - g.prototype.drawNodeShape = function(a, b, c, d, h, m, p) { - b.strokeStyle = d; - b.fillStyle = h; - h = l.NODE_TITLE_HEIGHT; - var e = a._shape || a.constructor.shape || l.BOX_SHAPE, f = a.constructor.title_mode, n = !0; - f == l.TRANSPARENT_TITLE ? n = !1 : f == l.AUTOHIDE_TITLE && p && (n = !0); - p = n ? -h : 0; - var k = c[0] + 1, q = n ? c[1] + h : c[1]; - a.flags.collapsed || (e == l.BOX_SHAPE || 0.5 > this.scale ? (b.beginPath(), b.rect(0, p, k, q), b.fill()) : e == l.ROUND_SHAPE || e == l.CARD_SHAPE ? (b.beginPath(), b.roundRect(0, p, k, q, this.round_radius, e == l.CARD_SHAPE ? 0 : this.round_radius), b.fill()) : e == l.CIRCLE_SHAPE && (b.beginPath(), b.arc(0.5 * c[0], 0.5 * c[1], 0.5 * c[0], 0, 2 * Math.PI), b.fill())); - b.shadowColor = "transparent"; - a.bgImage && a.bgImage.width && b.drawImage(a.bgImage, 0.5 * (c[0] - a.bgImage.width), 0.5 * (c[1] - a.bgImage.height)); - a.bgImageUrl && !a.bgImage && (a.bgImage = a.loadImage(a.bgImageUrl)); - a.onDrawBackground && (a.gui_rects && (a.gui_rects.length = 0), a.onDrawBackground(b, this)); - if (n || f == l.TRANSPARENT_TITLE) { - if (f != l.TRANSPARENT_TITLE) { - if (this.use_gradients) { - var r = g.gradients[d]; - r || (r = g.gradients[d] = b.createLinearGradient(0, 0, 400, 0), r.addColorStop(0, d), r.addColorStop(1, "#000")); - b.fillStyle = r; - } else { - b.fillStyle = d; - } - r = b.globalAlpha; - b.beginPath(); - if (e == l.BOX_SHAPE || 0.5 > this.scale) { - b.rect(0, -h, c[0] + 1, h), b.fill(); - } else { - if (e == l.ROUND_SHAPE || e == l.CARD_SHAPE) { - b.roundRect(0, -h, c[0] + 1, h, this.round_radius, a.flags.collapsed ? this.round_radius : 0), b.fill(); - } - } - } - b.fillStyle = a.boxcolor || l.NODE_DEFAULT_BOXCOLOR; - b.beginPath(); - e == l.ROUND_SHAPE || e == l.CIRCLE_SHAPE || e == l.CARD_SHAPE ? b.arc(0.5 * h, -0.5 * h, 0.5 * (h - 8), 0, 2 * Math.PI) : b.rect(4, -h + 4, h - 8, h - 8); - b.fill(); - b.globalAlpha = r; - 0.5 < this.scale && (b.font = this.title_text_font, r = a.getTitle()) && (b.fillStyle = m ? "white" : a.constructor.title_text_color || this.node_title_color, a.flags.collapsed ? (b.textAlign = "center", n = b.measureText(r), b.fillText(r, h + 0.5 * n.width, 0.2 * -h), b.textAlign = "left") : (b.textAlign = "left", b.fillText(r, h, 0.2 * -h))); - } - m && (f == l.TRANSPARENT_TITLE && (p -= h, q += h), b.lineWidth = 1, b.beginPath(), e == l.BOX_SHAPE ? b.rect(-6, -6 + p, 12 + k, 12 + q) : e == l.ROUND_SHAPE || e == l.CARD_SHAPE && a.flags.collapsed ? b.roundRect(-6, -6 + p, 12 + k, 12 + q, 2 * this.round_radius) : e == l.CARD_SHAPE ? b.roundRect(-6, -6 + p, 12 + k, 12 + q, 2 * this.round_radius, 2) : e == l.CIRCLE_SHAPE && b.arc(0.5 * c[0], 0.5 * c[1], 0.5 * c[0] + 6, 0, 2 * Math.PI), b.strokeStyle = "#DDD", b.stroke(), b.strokeStyle = d); - }; - g.prototype.drawConnections = function(a) { - var b = l.getTime(); - a.lineWidth = this.connections_width; - a.fillStyle = "#AAA"; - a.strokeStyle = "#AAA"; - a.globalAlpha = this.editor_alpha; - for (var c = 0, g = this.graph._nodes.length; c < g; ++c) { - var h = this.graph._nodes[c]; - if (h.inputs && h.inputs.length) { - for (var m = 0; m < h.inputs.length; ++m) { - var d = h.inputs[m]; - if (d && null != d.link && (d = this.graph.links[d.link])) { - var f = this.graph.getNodeById(d.origin_id); - if (null != f) { - var n = d.origin_slot; - f = -1 == n ? [f.pos[0] + 10, f.pos[1] + 10] : f.getConnectionPos(!1, n); - this.renderLink(a, f, h.getConnectionPos(!0, m), d); - if (d && d._last_time && 1000 > b - d._last_time) { - n = 2.0 - 0.002 * (b - d._last_time); - var k = "rgba(255,255,255, " + n.toFixed(2) + ")"; - this.renderLink(a, f, h.getConnectionPos(!0, m), d, !0, n, k); - } - } - } - } - } - } - a.globalAlpha = 1; - }; - g.prototype.renderLink = function(a, b, c, d, h, m, p) { - if (this.highquality_render) { - var e = q(b, c); - this.render_connections_border && 0.6 < this.scale && (a.lineWidth = this.connections_width + 4); - !p && d && (p = g.link_type_colors[d.type]); - p || (p = this.default_link_color); - null != d && this.highlighted_links[d.id] && (p = "#FFF"); - a.beginPath(); - this.render_curved_connections ? (a.moveTo(b[0], b[1]), a.bezierCurveTo(b[0] + 0.25 * e, b[1], c[0] - 0.25 * e, c[1], c[0], c[1])) : (a.moveTo(b[0] + 10, b[1]), a.lineTo(0.5 * (b[0] + 10 + (c[0] - 10)), b[1]), a.lineTo(0.5 * (b[0] + 10 + (c[0] - 10)), c[1]), a.lineTo(c[0] - 10, c[1])); - this.render_connections_border && 0.6 < this.scale && !h && (a.strokeStyle = "rgba(0,0,0,0.5)", a.stroke()); - a.lineWidth = this.connections_width; - a.fillStyle = a.strokeStyle = p; - a.stroke(); - this.render_connection_arrows && 0.6 <= this.scale && this.render_connection_arrows && 0.6 < this.scale && (d = this.computeConnectionPoint(b, c, 0.5), h = this.computeConnectionPoint(b, c, 0.51), h = this.render_curved_connections ? -Math.atan2(h[0] - d[0], h[1] - d[1]) : c[1] > b[1] ? 0 : Math.PI, a.save(), a.translate(d[0], d[1]), a.rotate(h), a.beginPath(), a.moveTo(-5, -5), a.lineTo(0, 5), a.lineTo(5, -5), a.fill(), a.restore()); - if (m) { - for (m = 0; 5 > m; ++m) { - d = (0.001 * l.getTime() + 0.2 * m) % 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(); - } - }; - g.prototype.computeConnectionPoint = function(a, b, c) { - var e = q(a, b), h = [a[0] + 0.25 * e, a[1]]; - e = [b[0] - 0.25 * e, b[1]]; - var m = (1 - c) * (1 - c) * (1 - c), d = 3 * (1 - c) * (1 - c) * c, g = 3 * (1 - c) * c * c; - c *= c * c; - return [m * a[0] + d * h[0] + g * e[0] + c * b[0], m * a[1] + d * h[1] + g * e[1] + c * b[1]]; - }; - g.prototype.drawNodeWidgets = function(a, b, c, d) { - if (!a.widgets || !a.widgets.length) { - return 0; - } - var e = a.size[0]; - a = a.widgets; - b += 2; - for (var m = l.NODE_WIDGET_HEIGHT, g = 0.5 < this.scale, f = 0; f < a.length; ++f) { - var n = a[f], k = b; - n.y && (k = n.y); - n.last_y = k; - c.strokeStyle = "#AAA"; - c.fillStyle = "#222"; - c.textAlign = "left"; - switch(n.type) { - case "button": - n.clicked && (c.fillStyle = "#AAA", n.clicked = !1, this.dirty_canvas = !0); - c.fillRect(10, k, e - 20, m); - c.strokeRect(10, k, e - 20, m); - g && (c.textAlign = "center", c.fillStyle = "#AAA", c.fillText(n.name, 0.5 * e, k + 0.7 * m)); - break; - case "slider": - c.fillStyle = "#111"; - c.fillRect(10, k, e - 20, m); - var q = (n.value - n.options.min) / (n.options.max - n.options.min); - c.fillStyle = d == n ? "#89A" : "#678"; - c.fillRect(10, k, q * (e - 20), m); - c.strokeRect(10, k, e - 20, m); - g && (c.textAlign = "center", c.fillStyle = "#DDD", c.fillText(n.name + " " + Number(n.value).toFixed(3), 0.5 * e, k + 0.7 * m)); - break; - case "number": - case "combo": - c.textAlign = "left", c.strokeStyle = "#AAA", c.fillStyle = "#111", c.beginPath(), c.roundRect(10, b, e - 20, m, 0.5 * m), c.fill(), c.stroke(), c.fillStyle = "#AAA", c.beginPath(), c.moveTo(26, b + 5), c.lineTo(16, b + 0.5 * m), c.lineTo(26, b + m - 5), c.moveTo(e - 26, b + 5), c.lineTo(e - 16, b + 0.5 * m), c.lineTo(e - 26, b + m - 5), c.fill(), g && (c.fillStyle = "#999", c.fillText(n.name, 30, k + 0.7 * m), c.fillStyle = "#DDD", c.textAlign = "right", "number" == n.type ? c.fillText(Number(n.value).toFixed(void 0 !== - n.options.precision ? n.options.precision : 3), e - 40, k + 0.7 * m) : c.fillText(n.value, e - 40, k + 0.7 * m)); - } - b += m + 4; - } - c.textAlign = "left"; - }; - g.prototype.processNodeWidgets = function(a, b, c, d) { - if (!a.widgets || !a.widgets.length) { - return null; - } - for (var e = b[0] - a.pos[0], m = b[1] - a.pos[1], g = a.size[0], f = this, n = 0; n < a.widgets.length; ++n) { - var k = a.widgets[n]; - if (k == d || 6 < e && e < g - 12 && m > k.last_y && m < k.last_y + l.NODE_WIDGET_HEIGHT) { - switch(k.type) { - case "button": - k.callback && setTimeout(function() { - k.callback(k, f, a, b); - }, 20); - this.dirty_canvas = k.clicked = !0; - break; - case "slider": - c = Math.clamp((e - 10) / (g - 20), 0, 1); - k.value = k.options.min + (k.options.max - k.options.min) * c; - k.callback && setTimeout(function() { - k.callback(k.value, f, a, b); - }, 20); - this.dirty_canvas = !0; - break; - case "number": - case "combo": - "mousemove" == c.type && "number" == k.type ? (k.value += 0.1 * c.deltaX * (k.options.step || 1), null != k.options.min && k.value < k.options.min && (k.value = k.options.min), null != k.options.max && k.value > k.options.max && (k.value = k.options.max)) : "mousedown" == c.type && (c = 40 > e ? -1 : e > g - 40 ? 1 : 0, "number" == k.type ? (k.value += 0.1 * c * (k.options.step || 1), null != k.options.min && k.value < k.options.min && (k.value = k.options.min), null != k.options.max && - k.value > k.options.max && (k.value = k.options.max)) : c && (c = k.options.values.indexOf(k.value) + c, c >= k.options.values.length && (c = 0), 0 > c && (c = k.options.values.length - 1), k.value = k.options.values[c])), k.callback && setTimeout(function() { - k.callback(k.value, f, a, b); - }, 20), this.dirty_canvas = !0; - } - return k; - } - } - return null; - }; - g.prototype.drawGroups = function(a, b) { - if (this.graph) { - a = this.graph._groups; - b.save(); - b.globalAlpha = 0.5; - b.font = "24px Arial"; - for (var c = 0; c < a.length; ++c) { - var d = a[c]; - if (v(this.visible_area, d._bounding)) { - b.fillStyle = d.color || "#335"; - b.strokeStyle = d.color || "#335"; - var h = d._pos, m = d._size; - b.globalAlpha = 0.25; - b.beginPath(); - b.rect(h[0] + 0.5, h[1] + 0.5, m[0], m[1]); - b.fill(); - b.globalAlpha = 1; - b.stroke(); - b.beginPath(); - b.moveTo(h[0] + m[0], h[1] + m[1]); - b.lineTo(h[0] + m[0] - 10, h[1] + m[1]); - b.lineTo(h[0] + m[0], h[1] + m[1] - 10); - b.fill(); - b.fillText(d.title, h[0] + 4, h[1] + 24); - } - } - b.restore(); - } - }; - g.prototype.resize = function(a, b) { - a || b || (b = this.canvas.parentNode, a = b.offsetWidth, b = b.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); - } - }; - g.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 < c && 0.99 < b.editor_alpha && (clearInterval(d), b.editor_alpha = 1); - }, 1); - } else { - this.live_mode = !this.live_mode, this.dirty_bgcanvas = this.dirty_canvas = !0; - } - }; - g.prototype.onNodeSelectionChange = function(a) { - }; - g.prototype.touchHandler = function(a) { - var b = a.changedTouches[0]; - switch(a.type) { - case "touchstart": - var c = "mousedown"; - break; - case "touchmove": - c = "mousemove"; - break; - case "touchend": - c = "mouseup"; - break; - default: - return; - } - var d = this.getCanvasWindow(), h = d.document.createEvent("MouseEvent"); - h.initMouseEvent(c, !0, !0, d, 1, b.screenX, b.screenY, b.clientX, b.clientY, !1, !1, !1, !1, 0, null); - b.target.dispatchEvent(h); - a.preventDefault(); - }; - g.onGroupAdd = function(a, b, c) { - a = g.active_canvas; - a.getCanvasWindow(); - b = new l.LGraphGroup; - b.pos = a.convertEventToCanvas(c); - a.graph.add(b); - }; - g.onMenuAdd = function(a, b, c, d) { - function e(a, b) { - b = d.getFirstEvent(); - if (a = l.createNode(a.value)) { - a.pos = m.convertEventToCanvas(b), m.graph.add(a); - } - } - var m = g.active_canvas, f = m.getCanvasWindow(); - a = l.getNodeTypesCategories(); - b = []; - for (var k in a) { - a[k] && b.push({value:a[k], content:a[k], has_submenu:!0}); - } - var n = new l.ContextMenu(b, {event:c, callback:function(a, b, c) { - a = l.getNodeTypesInCategory(a.value, m.filter); - b = []; - for (var h in a) { - b.push({content:a[h].title, value:a[h].type}); - } - new l.ContextMenu(b, {event:c, callback:e, parentMenu:n}, f); - return !1; - }, parentMenu:d}, f); - return !1; - }; - g.onMenuCollapseAll = function() { - }; - g.onMenuNodeEdit = function() { - }; - g.showMenuNodeOptionalInputs = function(a, b, c, d, h) { - if (h) { - var e = this; - a = g.active_canvas.getCanvasWindow(); - b = h.optional_inputs; - h.onGetInputs && (b = h.onGetInputs()); - var f = []; - if (b) { - for (var k in b) { - var n = b[k]; - if (n) { - var q = n[0]; - n[2] && n[2].label && (q = n[2].label); - q = {content:q, value:n}; - n[1] == l.ACTION && (q.className = "event"); - f.push(q); - } else { - f.push(null); - } - } - } - this.onMenuNodeInputs && (f = this.onMenuNodeInputs(f)); - if (f.length) { - return new l.ContextMenu(f, {event:c, callback:function(a, b, c) { - h && (a.callback && a.callback.call(e, h, a, b, c), a.value && (h.addInput(a.value[0], a.value[1], a.value[2]), h.setDirtyCanvas(!0, !0))); - }, parentMenu:d, node:h}, a), !1; - } - } - }; - g.showMenuNodeOptionalOutputs = function(a, b, c, d, h) { - function e(a, b, c) { - if (h && (a.callback && a.callback.call(f, h, a, b, c), a.value)) { - if (c = a.value[1], !c || c.constructor !== Object && c.constructor !== Array) { - h.addOutput(a.value[0], a.value[1], a.value[2]), h.setDirtyCanvas(!0, !0); - } else { - a = []; - for (var m in c) { - a.push({content:m, value:c[m]}); - } - new l.ContextMenu(a, {event:b, callback:e, parentMenu:d, node:h}); - return !1; - } - } - } - if (h) { - var f = this; - a = g.active_canvas.getCanvasWindow(); - b = h.optional_outputs; - h.onGetOutputs && (b = h.onGetOutputs()); - var k = []; - if (b) { - for (var n in b) { - var q = b[n]; - if (!q) { - k.push(null); - } else { - if (!h.flags || !h.flags.skip_repeated_outputs || -1 == h.findOutputSlot(q[0])) { - var r = q[0]; - q[2] && q[2].label && (r = q[2].label); - r = {content:r, value:q}; - q[1] == l.EVENT && (r.className = "event"); - k.push(r); - } - } - } - } - this.onMenuNodeOutputs && (k = this.onMenuNodeOutputs(k)); - if (k.length) { - return new l.ContextMenu(k, {event:c, callback:e, parentMenu:d, node:h}, a), !1; - } - } - }; - g.onShowMenuNodeProperties = function(a, b, c, d, h) { - if (h && h.properties) { - var e = g.active_canvas; - b = e.getCanvasWindow(); - var f = [], k; - for (k in h.properties) { - a = void 0 !== h.properties[k] ? h.properties[k] : " ", a = g.decodeHTML(a), f.push({content:"" + k + "" + a + "", value:k}); - } - if (f.length) { - return new l.ContextMenu(f, {event:c, callback:function(a, b, c, d) { - h && (b = this.getBoundingClientRect(), e.showEditPropertyValue(h, a.value, {position:[b.left, b.top]})); - }, parentMenu:d, allow_html:!0, node:h}, b), !1; - } - } - }; - g.decodeHTML = function(a) { - var b = document.createElement("div"); - b.innerText = a; - return b.innerHTML; - }; - g.onResizeNode = function(a, b, c, d, h) { - h && (h.size = h.computeSize(), h.setDirtyCanvas(!0, !0)); - }; - g.onShowTitleEditor = function(a, b, c, d, h) { - function e() { - h.title = l.value; - f.parentNode.removeChild(f); - h.setDirtyCanvas(!0, !0); - } - var f = document.createElement("div"); - f.className = "graphdialog"; - f.innerHTML = "Title"; - var l = f.querySelector("input"); - l && (l.value = h.title, l.addEventListener("keydown", function(a) { - 13 == a.keyCode && (e(), a.preventDefault(), a.stopPropagation()); - })); - a = g.active_canvas.canvas; - b = a.getBoundingClientRect(); - d = c = -20; - b && (c -= b.left, d -= b.top); - event ? (f.style.left = event.pageX + c + "px", f.style.top = event.pageY + d + "px") : (f.style.left = 0.5 * a.width + c + "px", f.style.top = 0.5 * a.height + d + "px"); - f.querySelector("button").addEventListener("click", e); - a.parentNode.appendChild(f); - }; - g.prototype.showSearchBox = function(a) { - function b(b) { - if (b) { - if (h.onSearchBoxSelection) { - h.onSearchBoxSelection(b, a, v); - } else { - if (b = l.createNode(b)) { - b.pos = v.convertEventToCanvas(a), v.graph.add(b); - } - } - } - m.close(); - } - function c(a) { - var b = q; - q && q.classList.remove("selected"); - q ? (q = a ? q.nextSibling : q.previousSibling) || (q = b) : q = a ? f.childNodes[0] : f.childNodes[f.childNodes.length]; - q && (q.classList.add("selected"), q.scrollIntoView()); - } - function d() { - n = null; - var a = r.value; - k = null; - f.innerHTML = ""; - if (a) { - if (h.onSearchBox) { - h.onSearchBox(e, a, v); - } else { - for (var c in l.registered_node_types) { - if (-1 != c.indexOf(a)) { - var e = document.createElement("div"); - k || (k = c); - e.innerText = c; - e.className = "litegraph lite-search-item"; - e.addEventListener("click", function(a) { - b(this.innerText); - }); - f.appendChild(e); - } - } - } - } - } - var h = this, m = document.createElement("div"); - m.className = "litegraph litesearchbox"; - m.innerHTML = "Search
"; - m.close = function() { - h.search_box = null; - m.parentNode.removeChild(m); - }; - m.addEventListener("mouseleave", function(a) { - m.close(); - }); - h.search_box && h.search_box.close(); - h.search_box = m; - var f = m.querySelector(".helper"), k = null, n = null, q = null, r = m.querySelector("input"); - r && r.addEventListener("keydown", function(a) { - if (38 == a.keyCode) { - c(!1); - } else { - if (40 == a.keyCode) { - c(!0); - } else { - if (27 == a.keyCode) { - m.close(); - } else { - if (13 == a.keyCode) { - q ? b(q.innerHTML) : k ? b(k) : m.close(); - } else { - n && clearInterval(n); - n = setTimeout(d, 10); - return; - } - } - } - } - a.preventDefault(); - a.stopPropagation(); - }); - var v = g.active_canvas, u = v.canvas, w = u.getBoundingClientRect(), z = -20, C = -20; - w && (z -= w.left, C -= w.top); - a ? (m.style.left = a.pageX + z + "px", m.style.top = a.pageY + C + "px") : (m.style.left = 0.5 * u.width + z + "px", m.style.top = 0.5 * u.height + C + "px"); - u.parentNode.appendChild(m); - r.focus(); - return m; - }; - g.prototype.showEditPropertyValue = function(a, b, c) { - function e() { - h(q.value); - } - function h(c) { - "number" == typeof a.properties[b] && (c = Number(c)); - "array" == d && (c = c.split(",").map(Number)); - a.properties[b] = c; - a._graph && a._graph._version++; - if (a.onPropertyChanged) { - a.onPropertyChanged(b, c); - } - n.close(); - a.setDirtyCanvas(!0, !0); - } - if (a && void 0 !== a.properties[b]) { - c = c || {}; - var d = "string"; - null !== a.properties[b] && (d = typeof a.properties[b]); - "object" == d && a.properties[b].length && (d = "array"); - var g = null; - a.getPropertyInfo && (g = a.getPropertyInfo(b)); - if (a.properties_info) { - for (var f = 0; f < a.properties_info.length; ++f) { - if (a.properties_info[f].name == b) { - g = a.properties_info[f]; - break; - } - } - } - void 0 !== g && null !== g && g.type && (d = g.type); - var l = ""; - if ("string" == d || "number" == d || "array" == d) { - l = ""; - } else { - if ("enum" == d && g.values) { - l = ""; - } else { - if ("boolean" == d) { - l = ""; - } else { - console.warn("unknown type: " + d); - return; - } - } - } - var n = this.createDialog("" + b + "" + l + "", c); - if ("enum" == d && g.values) { - var q = n.querySelector("select"); - q.addEventListener("change", function(a) { - h(a.target.value); - }); - } else { - if ("boolean" == d) { - (q = n.querySelector("input")) && q.addEventListener("click", function(a) { - h(!!q.checked); - }); - } else { - if (q = n.querySelector("input")) { - q.value = void 0 !== a.properties[b] ? a.properties[b] : "", q.addEventListener("keydown", function(a) { - 13 == a.keyCode && (e(), a.preventDefault(), a.stopPropagation()); - }); - } - } - } - n.querySelector("button").addEventListener("click", e); - } - }; - g.prototype.createDialog = function(a, b) { - b = b || {}; - var c = document.createElement("div"); - c.className = "graphdialog"; - c.innerHTML = a; - a = this.canvas.getBoundingClientRect(); - var d = -20, h = -20; - a && (d -= a.left, h -= a.top); - b.position ? (d += b.position[0], h += b.position[1]) : b.event ? (d += b.event.pageX, h += b.event.pageY) : (d += 0.5 * this.canvas.width, h += 0.5 * this.canvas.height); - c.style.left = d + "px"; - c.style.top = h + "px"; - this.canvas.parentNode.appendChild(c); - c.close = function() { - this.parentNode && this.parentNode.removeChild(this); - }; - return c; - }; - g.onMenuNodeCollapse = function(a, b, c, d, h) { - h.collapse(); - }; - g.onMenuNodePin = function(a, b, c, d, h) { - h.pin(); - }; - g.onMenuNodeMode = function(a, b, c, d, h) { - new l.ContextMenu(["Always", "On Event", "On Trigger", "Never"], {event:c, callback:function(a) { - if (h) { - switch(a) { - case "On Event": - h.mode = l.ON_EVENT; - break; - case "On Trigger": - h.mode = l.ON_TRIGGER; - break; - case "Never": - h.mode = l.NEVER; - break; - default: - h.mode = l.ALWAYS; - } - } - }, parentMenu:d, node:h}); - return !1; - }; - g.onMenuNodeColors = function(a, b, c, d, h) { - if (!h) { - throw "no node for color"; - } - b = []; - b.push({value:null, content:"No color"}); - for (var e in g.node_colors) { - a = g.node_colors[e], a = {value:e, content:"" + e + ""}, b.push(a); - } - new l.ContextMenu(b, {event:c, callback:function(a) { - h && ((a = a.value ? g.node_colors[a.value] : null) ? h.constructor === l.LGraphGroup ? h.color = a.groupcolor : (h.color = a.color, h.bgcolor = a.bgcolor) : (delete h.color, delete h.bgcolor), h.setDirtyCanvas(!0, !0)); - }, parentMenu:d, node:h}); - return !1; - }; - g.onMenuNodeShapes = function(a, b, c, d, h) { - if (!h) { - throw "no node passed"; - } - new l.ContextMenu(l.VALID_SHAPES, {event:c, callback:function(a) { - h && (h.shape = a, h.setDirtyCanvas(!0)); - }, parentMenu:d, node:h}); - return !1; - }; - g.onMenuNodeRemove = function(a, b, c, d, h) { - if (!h) { - throw "no node passed"; - } - !1 !== h.removable && (h.graph.remove(h), h.setDirtyCanvas(!0, !0)); - }; - g.onMenuNodeClone = function(a, b, c, d, h) { - 0 != h.clonable && (a = h.clone()) && (a.pos = [h.pos[0] + 5, h.pos[1] + 5], h.graph.add(a), h.setDirtyCanvas(!0, !0)); - }; - g.node_colors = {red:{color:"#322", bgcolor:"#533", groupcolor:"#A88"}, brown:{color:"#332922", bgcolor:"#593930", groupcolor:"#b06634"}, green:{color:"#232", bgcolor:"#353", groupcolor:"#8A8"}, blue:{color:"#223", bgcolor:"#335", groupcolor:"#88A"}, pale_blue:{color:"#2a363b", bgcolor:"#3f5159", groupcolor:"#3f789e"}, cyan:{color:"#233", bgcolor:"#355", groupcolor:"#8AA"}, purple:{color:"#323", bgcolor:"#535", groupcolor:"#a1309b"}, yellow:{color:"#432", bgcolor:"#653", groupcolor:"#b58b2a"}, - black:{color:"#222", bgcolor:"#000", groupcolor:"#444"}}; - g.prototype.getCanvasMenuOptions = function() { - if (this.getMenuOptions) { - var a = this.getMenuOptions(); - } else { - a = [{content:"Add Node", has_submenu:!0, callback:g.onMenuAdd}, {content:"Add Group", callback:g.onGroupAdd}], this._graph_stack && 0 < this._graph_stack.length && (a = [{content:"Close subgraph", callback:this.closeSubgraph.bind(this)}, null].concat(a)); - } - if (this.getExtraMenuOptions) { - var b = this.getExtraMenuOptions(this, a); - b && (a = a.concat(b)); - } - return a; - }; - g.prototype.getNodeMenuOptions = function(a) { - var b = a.getMenuOptions ? a.getMenuOptions(this) : [{content:"Inputs", has_submenu:!0, disabled:!0, callback:g.showMenuNodeOptionalInputs}, {content:"Outputs", has_submenu:!0, disabled:!0, callback:g.showMenuNodeOptionalOutputs}, null, {content:"Properties", has_submenu:!0, callback:g.onShowMenuNodeProperties}, null, {content:"Title", callback:g.onShowTitleEditor}, {content:"Mode", has_submenu:!0, callback:g.onMenuNodeMode}, {content:"Resize", callback:g.onResizeNode}, {content:"Collapse", callback:g.onMenuNodeCollapse}, - {content:"Pin", callback:g.onMenuNodePin}, {content:"Colors", has_submenu:!0, callback:g.onMenuNodeColors}, {content:"Shapes", has_submenu:!0, callback:g.onMenuNodeShapes}, null]; - if (a.getExtraMenuOptions) { - var c = a.getExtraMenuOptions(this); - c && (c.push(null), b = c.concat(b)); - } - !1 !== a.clonable && b.push({content:"Clone", callback:g.onMenuNodeClone}); - !1 !== a.removable && b.push(null, {content:"Remove", callback:g.onMenuNodeRemove}); - a.onGetInputs && (c = a.onGetInputs()) && c.length && (b[0].disabled = !1); - a.onGetOutputs && (c = a.onGetOutputs()) && c.length && (b[1].disabled = !1); - if (a.graph && a.graph.onGetNodeMenuOptions) { - a.graph.onGetNodeMenuOptions(b, a); - } - return b; - }; - g.prototype.getGroupMenuOptions = function(a) { - return [{content:"Title", callback:g.onShowTitleEditor}, {content:"Color", has_submenu:!0, callback:g.onMenuNodeColors}, null, {content:"Remove", callback:g.onMenuNodeRemove}]; - }; - g.prototype.processContextMenu = function(a, b) { - var c = this, d = g.active_canvas.getCanvasWindow(), h = null, m = {event:b, callback:function(b, e, d) { - if (b) { - if ("Remove Slot" == b.content) { - var h = b.slot; - h.input ? a.removeInput(h.slot) : h.output && a.removeOutput(h.slot); - } else { - if ("Rename Slot" == b.content) { - h = b.slot; - var g = c.createDialog("Name", e), m = g.querySelector("input"); - g.querySelector("button").addEventListener("click", function(b) { - if (m.value) { - if (b = h.input ? a.getInputInfo(h.slot) : a.getOutputInfo(h.slot)) { - b.label = m.value; - } - c.setDirty(!0); - } - g.close(); - }); - } - } - } - }, node:a}, f = null; - a && (f = a.getSlotInPosition(b.canvasX, b.canvasY), g.active_node = a); - f ? (h = [], h.push(f.locked ? "Cannot remove" : {content:"Remove Slot", slot:f}), h.push({content:"Rename Slot", slot:f}), m.title = (f.input ? f.input.type : f.output.type) || "*", f.input && f.input.type == l.ACTION && (m.title = "Action"), f.output && f.output.type == l.EVENT && (m.title = "Event")) : a ? h = this.getNodeMenuOptions(a) : (b = this.graph.getGroupOnPos(b.canvasX, b.canvasY)) ? (m.node = b, h = this.getGroupMenuOptions(b)) : h = this.getCanvasMenuOptions(); - h && new l.ContextMenu(h, m, d); - }; - this.CanvasRenderingContext2D && (CanvasRenderingContext2D.prototype.roundRect = function(a, b, c, d, h, g) { - void 0 === h && (h = 5); - void 0 === g && (g = h); - this.moveTo(a + h, b); - this.lineTo(a + c - h, b); - this.quadraticCurveTo(a + c, b, a + c, b + h); - this.lineTo(a + c, b + d - g); - this.quadraticCurveTo(a + c, b + d, a + c - g, b + d); - this.lineTo(a + g, b + d); - this.quadraticCurveTo(a, b + d, a, b + d - g); - this.lineTo(a, b + h); - this.quadraticCurveTo(a, b, a + h, b); - }); - l.compareObjects = function(a, b) { - for (var c in a) { - if (a[c] != b[c]) { - return !1; - } - } - return !0; - }; - l.distance = q; - 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 = r; - l.growBounding = function(a, b, c) { - b < a[0] ? a[0] = b : b > a[2] && (a[2] = b); - c < a[1] ? a[1] = c : c > a[3] && (a[3] = c); - }; - l.isInsideBounding = function(a, b) { - return a[0] < b[0][0] || a[1] < b[0][1] || a[0] > b[1][0] || a[1] > b[1][1] ? !1 : !0; - }; - l.overlapBounding = v; - l.hex2num = function(a) { - "#" == a.charAt(0) && (a = a.slice(1)); - a = a.toUpperCase(); - for (var b = Array(3), c = 0, d, h, g = 0; 6 > g; g += 2) { - d = "0123456789ABCDEF".indexOf(a.charAt(g)), h = "0123456789ABCDEF".indexOf(a.charAt(g + 1)), b[c] = 16 * d + h, c++; - } - return b; - }; - l.num2hex = function(a) { - for (var b = "#", c, d, h = 0; 3 > h; h++) { - c = a[h] / 16, d = a[h] % 16, b += "0123456789ABCDEF".charAt(c) + "0123456789ABCDEF".charAt(d); - } - return b; - }; - w.prototype.addItem = function(a, b, c) { - function e(a) { - var b = this.value; - b && b.has_submenu && d.call(this, a); - } - function d(a) { - var b = this.value, e = !0; - g.current_submenu && g.current_submenu.close(a); - if (c.callback) { - var d = c.callback.call(this, b, c, a, g, c.node); - !0 === d && (e = !1); - } - if (b && (b.callback && !c.ignore_item_callbacks && !0 !== b.disabled && (d = b.callback.call(this, b, c, a, g, c.node), !0 === d && (e = !1)), b.submenu)) { - if (!b.submenu.options) { - throw "ContextMenu submenu needs options"; - } - new g.constructor(b.submenu.options, {callback:b.submenu.callback, event:a, parentMenu:g, ignore_item_callbacks:b.submenu.ignore_item_callbacks, title:b.submenu.title, autoopen:c.autoopen}); - e = !1; - } - e && !g.lock && g.close(); - } - var g = this; - c = c || {}; - var f = document.createElement("div"); - f.className = "litemenu-entry submenu"; - var l = !1; - if (null === b) { - f.classList.add("separator"); - } else { - f.innerHTML = b && b.title ? b.title : a; - if (f.value = b) { - b.disabled && (l = !0, f.classList.add("disabled")), (b.submenu || b.has_submenu) && f.classList.add("has_submenu"); - } - "function" == typeof b ? (f.dataset.value = a, f.onclick_callback = b) : f.dataset.value = b; - b.className && (f.className += " " + b.className); - } - this.root.appendChild(f); - l || f.addEventListener("click", d); - c.autoopen && f.addEventListener("mouseenter", e); - return f; - }; - w.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 && !w.isCursorOverElement(a, this.parentMenu.root) && w.trigger(this.parentMenu.root, "mouseleave", a)); - this.current_submenu && this.current_submenu.close(a, !0); - }; - w.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; - }; - w.prototype.getTopMenu = function() { - return this.options.parentMenu ? this.options.parentMenu.getTopMenu() : this; - }; - w.prototype.getFirstEvent = function() { - return this.options.parentMenu ? this.options.parentMenu.getFirstEvent() : this.options.event; - }; - w.isCursorOverElement = function(a, b) { - var c = a.pageX; - a = a.pageY; - return (b = b.getBoundingClientRect()) ? a > b.top && a < b.top + b.height && c > b.left && c < b.left + b.width ? !0 : !1 : !1; - }; - l.ContextMenu = w; - l.closeAllContextMenus = function(a) { - a = a || window; - a = a.document.querySelectorAll(".litecontextmenu"); - if (a.length) { - for (var b = [], c = 0; c < a.length; c++) { - b.push(a[c]); - } - for (c in b) { - b[c].close ? b[c].close() : b[c].parentNode && b[c].parentNode.removeChild(b[c]); - } - } - }; - l.extendClass = function(a, b) { - for (var c in b) { - a.hasOwnProperty(c) || (a[c] = b[c]); - } - if (b.prototype) { - for (c in b.prototype) { - b.prototype.hasOwnProperty(c) && !a.prototype.hasOwnProperty(c) && (b.prototype.__lookupGetter__(c) ? a.prototype.__defineGetter__(c, b.prototype.__lookupGetter__(c)) : a.prototype[c] = b.prototype[c], b.prototype.__lookupSetter__(c) && a.prototype.__defineSetter__(c, b.prototype.__lookupSetter__(c))); - } - } - }; - l.getParameterNames = function(a) { - return (a + "").replace(/[/][/].*$/mg, "").replace(/\s+/g, "").replace(/[/][*][^/*]*[*][/]/g, "").split("){", 1)[0].replace(/^[^(]*[(]/, "").replace(/=[^,]+/g, "").split(",").filter(Boolean); - }; - Math.clamp = function(a, b, c) { - return b > a ? b : c < a ? c : a; - }; - "undefined" == typeof window || window.requestAnimationFrame || (window.requestAnimationFrame = window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function(a) { - window.setTimeout(a, 1000 / 60); - }); -})(this); -"undefined" != typeof exports && (exports.LiteGraph = this.LiteGraph); -(function(u) { - function f() { - this.addOutput("in ms", "number"); - this.addOutput("in sec", "number"); - } - function k() { - var c = this; - this.size = [120, 80]; - this.subgraph = new LGraph; - this.subgraph._subgraph_node = this; - this.subgraph._is_subgraph = !0; - this.subgraph.onGlobalInputAdded = this.onSubgraphNewGlobalInput.bind(this); - this.subgraph.onGlobalInputRenamed = this.onSubgraphRenamedGlobalInput.bind(this); - this.subgraph.onGlobalInputTypeChanged = this.onSubgraphTypeChangeGlobalInput.bind(this); - this.subgraph.onGlobalOutputAdded = this.onSubgraphNewGlobalOutput.bind(this); - this.subgraph.onGlobalOutputRenamed = this.onSubgraphRenamedGlobalOutput.bind(this); - this.subgraph.onGlobalOutputTypeChanged = this.onSubgraphTypeChangeGlobalOutput.bind(this); - this.addWidget("button", "Open Graph", null, function(a, b) { - b.openSubgraph(c.subgraph); - }); - this.bgcolor = "#353"; - } - function n() { - var c = "input_" + (1000 * Math.random()).toFixed(); - this.addOutput(c, null); - this.properties = {name:c, type:null}; - var a = this; - Object.defineProperty(this.properties, "name", {get:function() { - return c; - }, set:function(b) { - if ("" != b) { - var d = a.getOutputInfo(0); - d.name != b && (d.name = b, a.graph && a.graph.renameGlobalInput(c, b), c = b); - } - }, enumerable:!0}); - Object.defineProperty(this.properties, "type", {get:function() { - return a.outputs[0].type; - }, set:function(b) { - a.outputs[0].type = b; - a.graph && a.graph.changeGlobalInputType(c, a.outputs[0].type); - }, enumerable:!0}); - } - function g() { - var c = "output_" + (1000 * Math.random()).toFixed(); - this.addInput(c, null); - this._value = null; - this.properties = {name:c, type:null}; - var a = this; - Object.defineProperty(this.properties, "name", {get:function() { - return c; - }, set:function(b) { - if ("" != b) { - var d = a.getInputInfo(0); - d.name != b && (d.name = b, a.graph && a.graph.renameGlobalOutput(c, b), c = b); - } - }, enumerable:!0}); - Object.defineProperty(this.properties, "type", {get:function() { - return a.inputs[0].type; - }, set:function(b) { - a.inputs[0].type = b; - a.graph && a.graph.changeGlobalInputType(c, a.inputs[0].type); - }, enumerable:!0}); - } - function q() { - this.addOutput("value", "number"); - this.addProperty("value", 1.0); - this.editable = {property:"value", type:"number"}; - } - function r() { - this.size = [60, 20]; - this.addInput("value", 0, {label:""}); - this.addOutput("value", 0, {label:""}); - this.addProperty("value", ""); - } - function v() { - this.addInput("in", 0); - this.addOutput("out", 0); - this.size = [40, 20]; - } - function w() { - this.mode = d.ON_EVENT; - this.size = [60, 20]; - this.addProperty("msg", ""); - this.addInput("log", d.EVENT); - this.addInput("msg", 0); - } - function l() { - this.size = [60, 20]; - this.addProperty("onExecute", ""); - this.addInput("in", ""); - this.addInput("in2", ""); - this.addOutput("out", ""); - this.addOutput("out2", ""); - this._func = null; - } - var d = u.LiteGraph; - f.title = "Time"; - f.desc = "Time"; - f.prototype.onExecute = function() { - this.setOutputData(0, 1000 * this.graph.globaltime); - this.setOutputData(1, this.graph.globaltime); - }; - d.registerNodeType("basic/time", f); - k.title = "Subgraph"; - k.desc = "Graph inside a node"; - k.prototype.onSubgraphNewGlobalInput = function(c, a) { - this.addInput(c, a); - }; - k.prototype.onSubgraphRenamedGlobalInput = function(c, a) { - c = this.findInputSlot(c); - -1 != c && (this.getInputInfo(c).name = a); - }; - k.prototype.onSubgraphTypeChangeGlobalInput = function(c, a) { - c = this.findInputSlot(c); - -1 != c && (this.getInputInfo(c).type = a); - }; - k.prototype.onSubgraphNewGlobalOutput = function(c, a) { - this.addOutput(c, a); - }; - k.prototype.onSubgraphRenamedGlobalOutput = function(c, a) { - c = this.findOutputSlot(c); - -1 != c && (this.getOutputInfo(c).name = a); - }; - k.prototype.onSubgraphTypeChangeGlobalOutput = function(c, a) { - c = this.findOutputSlot(c); - -1 != c && (this.getOutputInfo(c).type = a); - }; - k.prototype.getExtraMenuOptions = function(c) { - var a = this; - return [{content:"Open", callback:function() { - c.openSubgraph(a.subgraph); - }}]; - }; - k.prototype.onDrawForeground = function(c, a) { - }; - k.prototype.onResize = function(c) { - c[1] += 20; - }; - k.prototype.onExecute = function() { - if (this.inputs) { - for (var c = 0; c < this.inputs.length; c++) { - var a = this.inputs[c], b = this.getInputData(c); - this.subgraph.setGlobalInputData(a.name, b); - } - } - this.subgraph.runStep(); - if (this.outputs) { - for (c = 0; c < this.outputs.length; c++) { - b = this.subgraph.getGlobalOutputData(this.outputs[c].name), this.setOutputData(c, b); - } - } - }; - k.prototype.configure = function(c) { - LGraphNode.prototype.configure.call(this, c); - }; - k.prototype.serialize = function() { - var c = LGraphNode.prototype.serialize.call(this); - c.subgraph = this.subgraph.serialize(); - return c; - }; - k.prototype.clone = function() { - var c = d.createNode(this.type), a = this.serialize(); - delete a.id; - delete a.inputs; - delete a.outputs; - c.configure(a); - return c; - }; - d.registerNodeType("graph/subgraph", k); - n.title = "Input"; - n.desc = "Input of the graph"; - n.prototype.onAdded = function() { - this.graph.addGlobalInput(this.properties.name, this.properties.type); - }; - n.prototype.onExecute = function() { - var c = this.graph.global_inputs[this.properties.name]; - c && this.setOutputData(0, c.value); - }; - d.registerNodeType("graph/input", n); - g.title = "Output"; - g.desc = "Output of the graph"; - g.prototype.onAdded = function() { - this.graph.addGlobalOutput(this.properties.name, this.properties.type); - }; - g.prototype.getValue = function() { - return this._value; - }; - g.prototype.onExecute = function() { - this._value = this.getInputData(0); - this.graph.setGlobalOutputData(this.properties.name, this._value); - }; - d.registerNodeType("graph/output", g); - q.title = "Const"; - q.desc = "Constant value"; - q.prototype.setValue = function(c) { - "string" == typeof c && (c = parseFloat(c)); - this.properties.value = c; - this.setDirtyCanvas(!0); - }; - q.prototype.onExecute = function() { - this.setOutputData(0, parseFloat(this.properties.value)); - }; - q.prototype.onDrawBackground = function(c) { - this.outputs[0].label = this.properties.value.toFixed(3); - }; - q.prototype.onWidget = function(c, a) { - "value" == a.name && this.setValue(a.value); - }; - d.registerNodeType("basic/const", q); - r.title = "Watch"; - r.desc = "Show value of input"; - r.prototype.onExecute = function() { - this.properties.value = this.getInputData(0); - this.setOutputData(0, this.properties.value); - }; - r.prototype.onDrawBackground = function(c) { - this.inputs[0] && null != this.properties.value && (this.inputs[0].label = this.properties.value.constructor === Number ? this.properties.value.toFixed(3) : String(this.properties.value)); - }; - d.registerNodeType("basic/watch", r); - v.title = "Pass"; - v.desc = "Allows to connect different types"; - v.prototype.onExecute = function() { - this.setOutputData(0, this.getInputData(0)); - }; - d.registerNodeType("basic/pass", v); - w.title = "Console"; - w.desc = "Show value inside the console"; - w.prototype.onAction = function(c, a) { - "log" == c ? console.log(a) : "warn" == c ? console.warn(a) : "error" == c && console.error(a); - }; - w.prototype.onExecute = function() { - var c = this.getInputData(1); - null !== c && (this.properties.msg = c); - console.log(c); - }; - w.prototype.onGetInputs = function() { - return [["log", d.ACTION], ["warn", d.ACTION], ["error", d.ACTION]]; - }; - d.registerNodeType("basic/console", w); - l.title = "Script"; - l.desc = "executes a code"; - l.widgets_info = {onExecute:{type:"code"}}; - l.prototype.onPropertyChanged = function(c, a) { - if ("onExecute" == c && d.allow_scripts) { - this._func = null; - try { - this._func = new Function(a); - } catch (b) { - console.error("Error parsing script"), console.error(b); - } - } - }; - l.prototype.onExecute = function() { - if (this._func) { - try { - this._func.call(this); - } catch (c) { - console.error("Error in script"), console.error(c); - } - } - }; - d.registerNodeType("basic/script", l); -})(this); -(function(u) { - function f() { - this.size = [60, 20]; - this.addInput("event", g.ACTION); - } - function k() { - this.size = [60, 20]; - this.addInput("event", g.ACTION); - this.addOutput("event", g.EVENT); - this.properties = {equal_to:"", has_property:"", property_equal_to:""}; - } - function n() { - this.size = [60, 20]; - this.addProperty("time", 1000); - this.addInput("event", g.ACTION); - this.addOutput("on_time", g.EVENT); - this._pending = []; - } - var g = u.LiteGraph; - f.title = "Log Event"; - f.desc = "Log event in console"; - f.prototype.onAction = function(g, f) { - console.log(g, f); - }; - g.registerNodeType("events/log", f); - k.title = "Filter Event"; - k.desc = "Blocks events that do not match the filter"; - k.prototype.onAction = function(g, f) { - if (null != f && (!this.properties.equal_to || this.properties.equal_to == f)) { - if (this.properties.has_property && (g = f[this.properties.has_property], null == g || this.properties.property_equal_to && this.properties.property_equal_to != g)) { - return; - } - this.triggerSlot(0, f); - } - }; - g.registerNodeType("events/filter", k); - n.title = "Delay"; - n.desc = "Delays one event"; - n.prototype.onAction = function(g, f) { - this._pending.push([this.properties.time, f]); - }; - n.prototype.onExecute = function() { - for (var g = 1000 * this.graph.elapsed_time, f = 0; f < this._pending.length; ++f) { - var k = this._pending[f]; - k[0] -= g; - 0 < k[0] || (this._pending.splice(f, 1), --f, this.trigger(null, k[1])); - } - }; - n.prototype.onGetInputs = function() { - return [["event", g.ACTION]]; - }; - g.registerNodeType("events/delay", n); -})(this); -(function(u) { - function f() { - this.addOutput("clicked", d.EVENT); - this.addProperty("text", ""); - this.addProperty("font_size", 40); - this.addProperty("message", ""); - this.size = [64, 84]; - } - function k() { - this.addInput("", "boolean"); - this.addInput("e", d.ACTION); - this.addOutput("v", "boolean"); - this.addOutput("e", d.EVENT); - this.properties = {font:"", value:!1}; - this.size = [124, 64]; - } - function n() { - this.addOutput("", "number"); - this.size = [74, 54]; - this.properties = {min:-1000, max:1000, value:1, step:1}; - this.old_y = -1; - this._precision = this._remainder = 0; - this.mouse_captured = !1; - } - function g() { - this.addOutput("", "number"); - this.size = [64, 84]; - this.properties = {min:0, max:1, value:0.5, wcolor:"#7AF", size:50}; - } - function q() { - this.addOutput("", "number"); - this.properties = {value:0.5, min:0, max:1, text:"V"}; - var c = this; - this.size = [80, 60]; - this.slider = this.addWidget("slider", "V", this.properties.value, function(a) { - c.properties.value = a; - }, this.properties); - } - function r() { - this.size = [160, 26]; - this.addOutput("", "number"); - this.properties = {wcolor:"#7AF", min:0, max:1, value:0.5}; - } - function v() { - this.size = [160, 26]; - this.addInput("", "number"); - this.properties = {min:0, max:1, value:0, wcolor:"#AAF"}; - } - function w() { - this.addInputs("", 0); - this.properties = {value:"...", font:"Arial", fontsize:18, color:"#AAA", align:"left", glowSize:0, decimals:1}; - } - function l() { - this.size = [200, 100]; - this.properties = {borderColor:"#ffffff", bgcolorTop:"#f0f0f0", bgcolorBottom:"#e0e0e0", shadowSize:2, borderRadius:3}; - } - var d = u.LiteGraph; - f.title = "Button"; - f.desc = "Triggers an event"; - f.font = "Arial"; - f.prototype.onDrawForeground = function(c) { - if (!this.flags.collapsed && (c.fillStyle = "black", c.fillRect(1, 1, this.size[0] - 3, this.size[1] - 3), c.fillStyle = "#AAF", c.fillRect(0, 0, this.size[0] - 3, this.size[1] - 3), c.fillStyle = this.clicked ? "white" : this.mouseOver ? "#668" : "#334", c.fillRect(1, 1, this.size[0] - 4, this.size[1] - 4), this.properties.text || 0 === this.properties.text)) { - var a = this.properties.font_size || 30; - c.textAlign = "center"; - c.fillStyle = this.clicked ? "black" : "white"; - c.font = a + "px " + f.font; - c.fillText(this.properties.text, 0.5 * this.size[0], 0.5 * this.size[1] + 0.3 * a); - c.textAlign = "left"; - } - }; - f.prototype.onMouseDown = function(c, a) { - if (1 < a[0] && 1 < a[1] && a[0] < this.size[0] - 2 && a[1] < this.size[1] - 2) { - return this.clicked = !0, this.trigger("clicked", this.properties.message), !0; - } - }; - f.prototype.onMouseUp = function(c) { - this.clicked = !1; - }; - d.registerNodeType("widget/button", f); - k.title = "Toggle"; - k.desc = "Toggles between true or false"; - k.prototype.onDrawForeground = function(c) { - if (!this.flags.collapsed) { - var a = 0.5 * this.size[1], b = 0.8 * this.size[1]; - c.fillStyle = "#AAA"; - c.fillRect(10, b - a, a, a); - c.fillStyle = this.properties.value ? "#AEF" : "#000"; - c.fillRect(10 + 0.25 * a, b - a + 0.25 * a, .5 * a, .5 * a); - c.textAlign = "left"; - c.font = this.properties.font || (0.8 * a).toFixed(0) + "px Arial"; - c.fillStyle = "#AAA"; - c.fillText(this.title, a + 20, 0.85 * b); - c.textAlign = "left"; - } - }; - k.prototype.onAction = function(c) { - this.properties.value = !this.properties.value; - this.trigger("e", this.properties.value); - }; - k.prototype.onExecute = function() { - var c = this.getInputData(0); - null != c && (this.properties.value = c); - this.setOutputData(0, this.properties.value); - }; - k.prototype.onMouseDown = function(c, a) { - if (1 < a[0] && 1 < a[1] && a[0] < this.size[0] - 2 && a[1] < this.size[1] - 2) { - return this.properties.value = !this.properties.value, this.graph._version++, this.trigger("e", this.properties.value), !0; - } - }; - d.registerNodeType("widget/toggle", k); - n.title = "Number"; - n.desc = "Widget to select number value"; - n.pixels_threshold = 10; - n.markers_color = "#666"; - n.prototype.onDrawForeground = function(c) { - var a = 0.5 * this.size[0], b = this.size[1]; - 30 < b ? (c.fillStyle = n.markers_color, c.beginPath(), c.moveTo(a, 0.1 * b), c.lineTo(a + 0.1 * b, 0.2 * b), c.lineTo(a + -0.1 * b, 0.2 * b), c.fill(), c.beginPath(), c.moveTo(a, 0.9 * b), c.lineTo(a + 0.1 * b, 0.8 * b), c.lineTo(a + -0.1 * b, 0.8 * b), c.fill(), c.font = (0.7 * b).toFixed(1) + "px Arial") : c.font = (0.8 * b).toFixed(1) + "px Arial"; - c.textAlign = "center"; - c.font = (0.7 * b).toFixed(1) + "px Arial"; - c.fillStyle = "#EEE"; - c.fillText(this.properties.value.toFixed(this._precision), a, 0.75 * b); - }; - n.prototype.onExecute = function() { - this.setOutputData(0, this.properties.value); - }; - n.prototype.onPropertyChanged = function(c, a) { - c = (this.properties.step + "").split("."); - this._precision = 1 < c.length ? c[1].length : 0; - }; - n.prototype.onMouseDown = function(c, a) { - if (!(0 > a[1])) { - return this.old_y = c.canvasY, this.captureInput(!0), this.mouse_captured = !0; - } - }; - n.prototype.onMouseMove = function(c) { - if (this.mouse_captured) { - var a = this.old_y - c.canvasY; - c.shiftKey && (a *= 10); - if (c.metaKey || c.altKey) { - a *= 0.1; - } - this.old_y = c.canvasY; - c = this._remainder + a / n.pixels_threshold; - this._remainder = c % 1; - c = Math.clamp(this.properties.value + (c | 0) * this.properties.step, this.properties.min, this.properties.max); - this.properties.value = c; - this.graph._version++; - this.setDirtyCanvas(!0); - } - }; - n.prototype.onMouseUp = function(c, a) { - 200 > c.click_time && (this.properties.value = Math.clamp(this.properties.value + (a[1] > 0.5 * this.size[1] ? -1 : 1) * this.properties.step, this.properties.min, this.properties.max), this.graph._version++, this.setDirtyCanvas(!0)); - this.mouse_captured && (this.mouse_captured = !1, this.captureInput(!1)); - }; - d.registerNodeType("widget/number", n); - g.title = "Knob"; - g.desc = "Circular controller"; - g.widgets = [{name:"increase", text:"+", type:"minibutton"}, {name:"decrease", text:"-", type:"minibutton"}]; - g.prototype.onAdded = function() { - this.value = (this.properties.value - this.properties.min) / (this.properties.max - this.properties.min); - this.imgbg = this.loadImage("imgs/knob_bg.png"); - this.imgfg = this.loadImage("imgs/knob_fg.png"); - }; - g.prototype.onDrawImageKnob = function(c) { - if (this.imgfg && this.imgfg.width) { - var a = 0.5 * this.imgbg.width, b = this.size[0] / this.imgfg.width; - c.save(); - c.translate(0, 20); - c.scale(b, b); - c.drawImage(this.imgbg, 0, 0); - c.translate(a, a); - c.rotate(2 * this.value * Math.PI * 6 / 8 + 10 * Math.PI / 8); - c.translate(-a, -a); - c.drawImage(this.imgfg, 0, 0); - c.restore(); - this.title && (c.font = "bold 16px Criticized,Tahoma", c.fillStyle = "rgba(100,100,100,0.8)", c.textAlign = "center", c.fillText(this.title.toUpperCase(), 0.5 * this.size[0], 18), c.textAlign = "left"); - } - }; - g.prototype.onDrawVectorKnob = function(c) { - if (this.imgfg && this.imgfg.width) { - c.lineWidth = 1; - c.strokeStyle = this.mouseOver ? "#FFF" : "#AAA"; - c.fillStyle = "#000"; - c.beginPath(); - c.arc(0.5 * this.size[0], 0.5 * this.size[1] + 10, 0.5 * this.properties.size, 0, 2 * Math.PI, !0); - c.stroke(); - 0 < this.value && (c.strokeStyle = this.properties.wcolor, c.lineWidth = 0.2 * this.properties.size, c.beginPath(), c.arc(0.5 * this.size[0], 0.5 * this.size[1] + 10, 0.35 * this.properties.size, -0.5 * Math.PI + 2 * Math.PI * this.value, -0.5 * Math.PI, !0), c.stroke(), c.lineWidth = 1); - c.font = 0.2 * this.properties.size + "px Arial"; - c.fillStyle = "#AAA"; - c.textAlign = "center"; - var a = this.properties.value; - "number" == typeof a && (a = a.toFixed(2)); - c.fillText(a, 0.5 * this.size[0], 0.65 * this.size[1]); - c.textAlign = "left"; - } - }; - g.prototype.onDrawForeground = function(c) { - this.onDrawImageKnob(c); - }; - g.prototype.onExecute = function() { - this.setOutputData(0, this.properties.value); - this.boxcolor = d.colorToString([this.value, this.value, this.value]); - }; - g.prototype.onMouseDown = function(c) { - if (this.imgfg && this.imgfg.width) { - this.center = [0.5 * this.size[0], 0.5 * this.size[1] + 20]; - this.radius = 0.5 * this.size[0]; - if (20 > c.canvasY - this.pos[1] || d.distance([c.canvasX, c.canvasY], [this.pos[0] + this.center[0], this.pos[1] + this.center[1]]) > this.radius) { - return !1; - } - this.oldmouse = [c.canvasX - this.pos[0], c.canvasY - this.pos[1]]; - this.captureInput(!0); - return !0; - } - }; - g.prototype.onMouseMove = function(c) { - if (this.oldmouse) { - c = [c.canvasX - this.pos[0], c.canvasY - this.pos[1]]; - var a = this.value; - a -= 0.01 * (c[1] - this.oldmouse[1]); - 1.0 < a ? a = 1.0 : 0.0 > a && (a = 0.0); - this.value = a; - this.properties.value = this.properties.min + (this.properties.max - this.properties.min) * this.value; - this.oldmouse = c; - this.setDirtyCanvas(!0); - } - }; - g.prototype.onMouseUp = function(c) { - this.oldmouse && (this.oldmouse = null, this.captureInput(!1)); - }; - g.prototype.onMouseLeave = function(c) { - }; - g.prototype.onWidget = function(c, a) { - if ("increase" == a.name) { - this.onPropertyChanged("size", this.properties.size + 10); - } else { - if ("decrease" == a.name) { - this.onPropertyChanged("size", this.properties.size - 10); - } - } - }; - g.prototype.onPropertyChanged = function(c, a) { - if ("wcolor" == c) { - this.properties[c] = a; - } else { - if ("size" == c) { - a = parseInt(a), this.properties[c] = a, this.size = [a + 4, a + 24], this.setDirtyCanvas(!0, !0); - } else { - if ("min" == c || "max" == c || "value" == c) { - this.properties[c] = parseFloat(a); - } else { - return !1; - } - } - } - return !0; - }; - d.registerNodeType("widget/knob", g); - q.title = "Internal Slider"; - q.prototype.onPropertyChanged = function(c, a) { - "value" == c && (this.slider.value = a); - }; - q.prototype.onExecute = function() { - this.setOutputData(0, this.properties.value); - }; - d.registerNodeType("widget/internal_slider", q); - r.title = "H.Slider"; - r.desc = "Linear slider controller"; - r.prototype.onAdded = function() { - this.value = 0.5; - this.imgfg = this.loadImage("imgs/slider_fg.png"); - }; - r.prototype.onDrawVectorial = function(c) { - this.imgfg && this.imgfg.width && (c.lineWidth = 1, c.strokeStyle = this.mouseOver ? "#FFF" : "#AAA", c.fillStyle = "#000", c.beginPath(), c.rect(2, 0, this.size[0] - 4, 20), c.stroke(), c.fillStyle = this.properties.wcolor, c.beginPath(), c.rect(2 + (this.size[0] - 4 - 20) * this.value, 0, 20, 20), c.fill()); - }; - r.prototype.onDrawImage = function(c) { - this.imgfg && this.imgfg.width && (c.lineWidth = 1, c.fillStyle = "#000", c.fillRect(2, 9, this.size[0] - 4, 2), c.strokeStyle = "#333", c.beginPath(), c.moveTo(2, 9), c.lineTo(this.size[0] - 4, 9), c.stroke(), c.strokeStyle = "#AAA", c.beginPath(), c.moveTo(2, 11), c.lineTo(this.size[0] - 4, 11), c.stroke(), c.drawImage(this.imgfg, 2 + (this.size[0] - 4) * this.value - 0.5 * this.imgfg.width, 0.5 * -this.imgfg.height + 10)); - }; - r.prototype.onDrawForeground = function(c) { - this.onDrawImage(c); - }; - r.prototype.onExecute = function() { - this.properties.value = this.properties.min + (this.properties.max - this.properties.min) * this.value; - this.setOutputData(0, this.properties.value); - this.boxcolor = d.colorToString([this.value, this.value, this.value]); - }; - r.prototype.onMouseDown = function(c) { - if (0 > c.canvasY - this.pos[1]) { - return !1; - } - this.oldmouse = [c.canvasX - this.pos[0], c.canvasY - this.pos[1]]; - this.captureInput(!0); - return !0; - }; - r.prototype.onMouseMove = function(c) { - if (this.oldmouse) { - c = [c.canvasX - this.pos[0], c.canvasY - this.pos[1]]; - var a = this.value; - a += (c[0] - this.oldmouse[0]) / this.size[0]; - 1.0 < a ? a = 1.0 : 0.0 > a && (a = 0.0); - this.value = a; - this.oldmouse = c; - this.setDirtyCanvas(!0); - } - }; - r.prototype.onMouseUp = function(c) { - this.oldmouse = null; - this.captureInput(!1); - }; - r.prototype.onMouseLeave = function(c) { - }; - r.prototype.onPropertyChanged = function(c, a) { - if ("wcolor" == c) { - this.properties[c] = a; - } else { - return !1; - } - return !0; - }; - d.registerNodeType("widget/hslider", r); - v.title = "Progress"; - v.desc = "Shows data in linear progress"; - v.prototype.onExecute = function() { - var c = this.getInputData(0); - void 0 != c && (this.properties.value = c); - }; - v.prototype.onDrawForeground = function(c) { - c.lineWidth = 1; - c.fillStyle = this.properties.wcolor; - var a = (this.properties.value - this.properties.min) / (this.properties.max - this.properties.min); - a = Math.min(1, a); - a = Math.max(0, a); - c.fillRect(2, 2, (this.size[0] - 4) * a, this.size[1] - 4); - }; - d.registerNodeType("widget/progress", v); - w.title = "Text"; - w.desc = "Shows the input value"; - w.widgets = [{name:"resize", text:"Resize box", type:"button"}, {name:"led_text", text:"LED", type:"minibutton"}, {name:"normal_text", text:"Normal", type:"minibutton"}]; - w.prototype.onDrawForeground = function(c) { - c.fillStyle = this.properties.color; - var a = this.properties.value; - this.properties.glowSize ? (c.shadowColor = this.properties.color, c.shadowOffsetX = 0, c.shadowOffsetY = 0, c.shadowBlur = this.properties.glowSize) : c.shadowColor = "transparent"; - var b = this.properties.fontsize; - c.textAlign = this.properties.align; - c.font = b.toString() + "px " + this.properties.font; - this.str = "number" == typeof a ? a.toFixed(this.properties.decimals) : a; - if ("string" == typeof this.str) { - a = this.str.split("\\n"); - for (var d in a) { - c.fillText(a[d], "left" == this.properties.align ? 15 : this.size[0] - 15, -0.15 * b + b * (parseInt(d) + 1)); - } - } - c.shadowColor = "transparent"; - this.last_ctx = c; - c.textAlign = "left"; - }; - w.prototype.onExecute = function() { - var c = this.getInputData(0); - null != c && (this.properties.value = c); - }; - w.prototype.resize = function() { - if (this.last_ctx) { - var c = this.str.split("\\n"); - this.last_ctx.font = this.properties.fontsize + "px " + this.properties.font; - var a = 0, b; - for (b in c) { - var d = this.last_ctx.measureText(c[b]).width; - a < d && (a = d); - } - this.size[0] = a + 20; - this.size[1] = 4 + c.length * this.properties.fontsize; - this.setDirtyCanvas(!0); - } - }; - w.prototype.onWidget = function(c, a) { - "resize" == a.name ? this.resize() : "led_text" == a.name ? (this.properties.font = "Digital", this.properties.glowSize = 4, this.setDirtyCanvas(!0)) : "normal_text" == a.name && (this.properties.font = "Arial", this.setDirtyCanvas(!0)); - }; - w.prototype.onPropertyChanged = function(c, a) { - this.properties[c] = a; - this.str = "number" == typeof a ? a.toFixed(3) : a; - return !0; - }; - d.registerNodeType("widget/text", w); - l.title = "Panel"; - l.desc = "Non interactive panel"; - l.widgets = [{name:"update", text:"Update", type:"button"}]; - l.prototype.createGradient = function(c) { - "" == this.properties.bgcolorTop || "" == this.properties.bgcolorBottom ? this.lineargradient = 0 : (this.lineargradient = c.createLinearGradient(0, 0, 0, this.size[1]), this.lineargradient.addColorStop(0, this.properties.bgcolorTop), this.lineargradient.addColorStop(1, this.properties.bgcolorBottom)); - }; - l.prototype.onDrawForeground = function(c) { - null == this.lineargradient && this.createGradient(c); - this.lineargradient && (c.lineWidth = 1, c.strokeStyle = this.properties.borderColor, c.fillStyle = this.lineargradient, this.properties.shadowSize ? (c.shadowColor = "#000", c.shadowOffsetX = 0, c.shadowOffsetY = 0, c.shadowBlur = this.properties.shadowSize) : c.shadowColor = "transparent", c.roundRect(0, 0, this.size[0] - 1, this.size[1] - 1, this.properties.shadowSize), c.fill(), c.shadowColor = "transparent", c.stroke()); - }; - l.prototype.onWidget = function(c, a) { - "update" == a.name && (this.lineargradient = null, this.setDirtyCanvas(!0)); - }; - d.registerNodeType("widget/panel", l); -})(this); -(function(u) { - function f() { - this.addOutput("left_x_axis", "number"); - this.addOutput("left_y_axis", "number"); - this.addOutput("button_pressed", k.EVENT); - this.properties = {gamepad_index:0, threshold:0.1}; - this._left_axis = new Float32Array(2); - this._right_axis = new Float32Array(2); - this._triggers = new Float32Array(2); - this._previous_buttons = new Uint8Array(17); - this._current_buttons = new Uint8Array(17); - } - var k = u.LiteGraph; - f.title = "Gamepad"; - f.desc = "gets the input of the gamepad"; - f.zero = new Float32Array(2); - f.buttons = "a b x y lb rb lt rt back start ls rs home".split(" "); - f.prototype.onExecute = function() { - var k = this.getGamepad(), g = this.properties.threshold || 0.0; - k && (this._left_axis[0] = Math.abs(k.xbox.axes.lx) > g ? k.xbox.axes.lx : 0, this._left_axis[1] = Math.abs(k.xbox.axes.ly) > g ? k.xbox.axes.ly : 0, this._right_axis[0] = Math.abs(k.xbox.axes.rx) > g ? k.xbox.axes.rx : 0, this._right_axis[1] = Math.abs(k.xbox.axes.ry) > g ? k.xbox.axes.ry : 0, this._triggers[0] = Math.abs(k.xbox.axes.ltrigger) > g ? k.xbox.axes.ltrigger : 0, this._triggers[1] = Math.abs(k.xbox.axes.rtrigger) > g ? k.xbox.axes.rtrigger : 0); - if (this.outputs) { - for (g = 0; g < this.outputs.length; g++) { - var q = this.outputs[g]; - if (q.links && q.links.length) { - var r = null; - if (k) { - switch(q.name) { - case "left_axis": - r = this._left_axis; - break; - case "right_axis": - r = this._right_axis; - break; - case "left_x_axis": - r = this._left_axis[0]; - break; - case "left_y_axis": - r = this._left_axis[1]; - break; - case "right_x_axis": - r = this._right_axis[0]; - break; - case "right_y_axis": - r = this._right_axis[1]; - break; - case "trigger_left": - r = this._triggers[0]; - break; - case "trigger_right": - r = this._triggers[1]; - break; - case "a_button": - r = k.xbox.buttons.a ? 1 : 0; - break; - case "b_button": - r = k.xbox.buttons.b ? 1 : 0; - break; - case "x_button": - r = k.xbox.buttons.x ? 1 : 0; - break; - case "y_button": - r = k.xbox.buttons.y ? 1 : 0; - break; - case "lb_button": - r = k.xbox.buttons.lb ? 1 : 0; - break; - case "rb_button": - r = k.xbox.buttons.rb ? 1 : 0; - break; - case "ls_button": - r = k.xbox.buttons.ls ? 1 : 0; - break; - case "rs_button": - r = k.xbox.buttons.rs ? 1 : 0; - break; - case "start_button": - r = k.xbox.buttons.start ? 1 : 0; - break; - case "back_button": - r = k.xbox.buttons.back ? 1 : 0; - break; - case "button_pressed": - for (q = 0; q < this._current_buttons.length; ++q) { - this._current_buttons[q] && !this._previous_buttons[q] && this.triggerSlot(g, f.buttons[q]); - } - } - } else { - switch(q.name) { - case "button_pressed": - break; - case "left_axis": - case "right_axis": - r = f.zero; - break; - default: - r = 0; - } - } - this.setOutputData(g, r); - } - } - } - }; - f.prototype.getGamepad = function() { - var f = navigator.getGamepads || navigator.webkitGetGamepads || navigator.mozGetGamepads; - if (!f) { - return null; - } - f = f.call(navigator); - this._previous_buttons.set(this._current_buttons); - for (var g = this.properties.gamepad_index; 4 > g; g++) { - if (f[g]) { - f = f[g]; - g = this.xbox_mapping; - g || (g = this.xbox_mapping = {axes:[], buttons:{}, hat:""}); - g.axes.lx = f.axes[0]; - g.axes.ly = f.axes[1]; - g.axes.rx = f.axes[2]; - g.axes.ry = f.axes[3]; - g.axes.ltrigger = f.buttons[6].value; - g.axes.rtrigger = f.buttons[7].value; - for (var k = 0; k < f.buttons.length; k++) { - switch(this._current_buttons[k] = f.buttons[k].pressed, k) { - case 0: - g.buttons.a = f.buttons[k].pressed; - break; - case 1: - g.buttons.b = f.buttons[k].pressed; - break; - case 2: - g.buttons.x = f.buttons[k].pressed; - break; - case 3: - g.buttons.y = f.buttons[k].pressed; - break; - case 4: - g.buttons.lb = f.buttons[k].pressed; - break; - case 5: - g.buttons.rb = f.buttons[k].pressed; - break; - case 6: - g.buttons.lt = f.buttons[k].pressed; - break; - case 7: - g.buttons.rt = f.buttons[k].pressed; - break; - case 8: - g.buttons.back = f.buttons[k].pressed; - break; - case 9: - g.buttons.start = f.buttons[k].pressed; - break; - case 10: - g.buttons.ls = f.buttons[k].pressed; - break; - case 11: - g.buttons.rs = f.buttons[k].pressed; - break; - case 12: - f.buttons[k].pressed && (g.hat += "up"); - break; - case 13: - f.buttons[k].pressed && (g.hat += "down"); - break; - case 14: - f.buttons[k].pressed && (g.hat += "left"); - break; - case 15: - f.buttons[k].pressed && (g.hat += "right"); - break; - case 16: - g.buttons.home = f.buttons[k].pressed; - } - } - f.xbox = g; - return f; - } - } - }; - f.prototype.onDrawBackground = function(f) { - var g = this._left_axis, k = this._right_axis; - f.strokeStyle = "#88A"; - f.strokeRect(0.5 * (g[0] + 1) * this.size[0] - 4, 0.5 * (g[1] + 1) * this.size[1] - 4, 8, 8); - f.strokeStyle = "#8A8"; - f.strokeRect(0.5 * (k[0] + 1) * this.size[0] - 4, 0.5 * (k[1] + 1) * this.size[1] - 4, 8, 8); - g = this.size[1] / this._current_buttons.length; - f.fillStyle = "#AEB"; - for (k = 0; k < this._current_buttons.length; ++k) { - this._current_buttons[k] && f.fillRect(0, g * k, 6, g); - } - }; - f.prototype.onGetOutputs = function() { - return [["left_axis", "vec2"], ["right_axis", "vec2"], ["left_x_axis", "number"], ["left_y_axis", "number"], ["right_x_axis", "number"], ["right_y_axis", "number"], ["trigger_left", "number"], ["trigger_right", "number"], ["a_button", "number"], ["b_button", "number"], ["x_button", "number"], ["y_button", "number"], ["lb_button", "number"], ["rb_button", "number"], ["ls_button", "number"], ["rs_button", "number"], ["start", "number"], ["back", "number"], ["button_pressed", k.EVENT]]; - }; - k.registerNodeType("input/gamepad", f); -})(this); -(function(u) { - function f() { - this.addInput("in", "*"); - this.size = [60, 20]; - } - function k() { - this.addInput("in"); - this.addOutput("out"); - this.size = [60, 20]; - } - function n() { - this.addInput("in", "number", {locked:!0}); - this.addOutput("out", "number", {locked:!0}); - this.addProperty("in", 0); - this.addProperty("in_min", 0); - this.addProperty("in_max", 1); - this.addProperty("out_min", 0); - this.addProperty("out_max", 1); - } - function g() { - this.addOutput("value", "number"); - this.addProperty("min", 0); - this.addProperty("max", 1); - this.size = [60, 20]; - } - function q() { - this.addInput("in", "number"); - this.addOutput("out", "number"); - this.size = [60, 20]; - this.addProperty("min", 0); - this.addProperty("max", 1); - } - function r() { - this.properties = {f:0.5}; - this.addInput("A", "number"); - this.addInput("B", "number"); - this.addOutput("out", "number"); - } - function v() { - this.addInput("in", "number"); - this.addOutput("out", "number"); - this.size = [60, 20]; - } - function w() { - this.addInput("in", "number"); - this.addOutput("out", "number"); - this.size = [60, 20]; - } - function l() { - this.addInput("in", "number"); - this.addOutput("out", "number"); - this.size = [60, 20]; - } - function d() { - this.addInput("in", "number"); - this.addOutput("out", "number"); - this.size = [60, 20]; - this.properties = {A:0, B:1}; - } - function c() { - this.addInput("in", "number", {label:""}); - this.addOutput("out", "number", {label:""}); - this.size = [60, 20]; - this.addProperty("factor", 1); - } - function a() { - this.addInput("in", "number"); - this.addOutput("out", "number"); - this.size = [60, 20]; - this.addProperty("samples", 10); - this._values = new Float32Array(10); - this._current = 0; - } - function b() { - this.addInput("in", "number"); - this.addOutput("out", "number"); - this.addProperty("factor", 0.1); - this.size = [60, 20]; - this._value = null; - } - function e() { - this.addInput("A", "number"); - this.addInput("B", "number"); - this.addOutput("=", "number"); - this.addProperty("A", 1); - this.addProperty("B", 1); - this.addProperty("OP", "+", "enum", {values:e.values}); - } - function y() { - this.addInput("A", "number"); - this.addInput("B", "number"); - this.addOutput("A==B", "boolean"); - this.addOutput("A!=B", "boolean"); - this.addProperty("A", 0); - this.addProperty("B", 0); - } - function h() { - this.addInput("A", "number"); - this.addInput("B", "number"); - this.addOutput("out", "boolean"); - this.addProperty("A", 1); - this.addProperty("B", 1); - this.addProperty("OP", ">", "string", {values:h.values}); - this.size = [60, 40]; - } - function m() { - this.addInput("inc", "number"); - this.addOutput("total", "number"); - this.addProperty("increment", 1); - this.addProperty("value", 0); - } - function p() { - this.addInput("v", "number"); - this.addOutput("sin", "number"); - this.addProperty("amplitude", 1); - this.addProperty("offset", 0); - this.bgImageUrl = "nodes/imgs/icon-sin.png"; - } - function B() { - this.addInput("vec2", "vec2"); - this.addOutput("x", "number"); - this.addOutput("y", "number"); - } - function G() { - this.addInputs([["x", "number"], ["y", "number"]]); - this.addOutput("vec2", "vec2"); - this.properties = {x:0, y:0}; - this._data = new Float32Array(2); - } - function D() { - this.addInput("vec3", "vec3"); - this.addOutput("x", "number"); - this.addOutput("y", "number"); - this.addOutput("z", "number"); - } - function E() { - this.addInputs([["x", "number"], ["y", "number"], ["z", "number"]]); - this.addOutput("vec3", "vec3"); - this.properties = {x:0, y:0, z:0}; - this._data = new Float32Array(3); - } - function H() { - this.addInput("vec4", "vec4"); - this.addOutput("x", "number"); - this.addOutput("y", "number"); - this.addOutput("z", "number"); - this.addOutput("w", "number"); - } - function A() { - this.addInputs([["x", "number"], ["y", "number"], ["z", "number"], ["w", "number"]]); - this.addOutput("vec4", "vec4"); - this.properties = {x:0, y:0, z:0, w:0}; - this._data = new Float32Array(4); - } - var x = u.LiteGraph; - f.title = "Converter"; - f.desc = "type A to type B"; - f.prototype.onExecute = function() { - var a = this.getInputData(0); - if (null != a && this.outputs) { - for (var b = 0; b < this.outputs.length; b++) { - var c = this.outputs[b]; - if (c.links && c.links.length) { - var d = null; - switch(c.name) { - case "number": - d = a.length ? a[0] : parseFloat(a); - break; - case "vec2": - case "vec3": - case "vec4": - d = 1; - switch(c.name) { - case "vec2": - d = 2; - break; - case "vec3": - d = 3; - break; - case "vec4": - d = 4; - }d = new Float32Array(d); - if (a.length) { - for (c = 0; c < a.length && c < d.length; c++) { - d[c] = a[c]; - } - } else { - d[0] = parseFloat(a); - } - } - this.setOutputData(b, d); - } - } - } - }; - f.prototype.onGetOutputs = function() { - return [["number", "number"], ["vec2", "vec2"], ["vec3", "vec3"], ["vec4", "vec4"]]; - }; - x.registerNodeType("math/converter", f); - k.title = "Bypass"; - k.desc = "removes the type"; - k.prototype.onExecute = function() { - var a = this.getInputData(0); - this.setOutputData(0, a); - }; - x.registerNodeType("math/bypass", k); - n.title = "Range"; - n.desc = "Convert a number from one range to another"; - n.prototype.onExecute = function() { - if (this.inputs) { - for (var a = 0; a < this.inputs.length; a++) { - var b = this.inputs[a], c = this.getInputData(a); - void 0 !== c && (this.properties[b.name] = c); - } - } - c = this.properties["in"]; - if (void 0 === c || null === c || c.constructor !== Number) { - c = 0; - } - a = this.properties.in_min; - b = this.properties.out_min; - this._last_v = (c - a) / (this.properties.in_max - a) * (this.properties.out_max - b) + b; - this.setOutputData(0, this._last_v); - }; - n.prototype.onDrawBackground = function(a) { - this.outputs[0].label = this._last_v ? this._last_v.toFixed(3) : "?"; - }; - n.prototype.onGetInputs = function() { - return [["in_min", "number"], ["in_max", "number"], ["out_min", "number"], ["out_max", "number"]]; - }; - x.registerNodeType("math/range", n); - g.title = "Rand"; - g.desc = "Random number"; - g.prototype.onExecute = function() { - if (this.inputs) { - for (var a = 0; a < this.inputs.length; a++) { - var b = this.inputs[a], c = this.getInputData(a); - void 0 !== c && (this.properties[b.name] = c); - } - } - a = this.properties.min; - this._last_v = Math.random() * (this.properties.max - a) + a; - this.setOutputData(0, this._last_v); - }; - g.prototype.onDrawBackground = function(a) { - this.outputs[0].label = this._last_v ? this._last_v.toFixed(3) : "?"; - }; - g.prototype.onGetInputs = function() { - return [["min", "number"], ["max", "number"]]; - }; - x.registerNodeType("math/rand", g); - q.title = "Clamp"; - q.desc = "Clamp number between min and max"; - q.filter = "shader"; - q.prototype.onExecute = function() { - var a = this.getInputData(0); - null != a && (a = Math.max(this.properties.min, a), a = Math.min(this.properties.max, a), this.setOutputData(0, a)); - }; - q.prototype.getCode = function(a) { - a = ""; - this.isInputConnected(0) && (a += "clamp({{0}}," + this.properties.min + "," + this.properties.max + ")"); - return a; - }; - x.registerNodeType("math/clamp", q); - r.title = "Lerp"; - r.desc = "Linear Interpolation"; - r.prototype.onExecute = function() { - var a = this.getInputData(0); - null == a && (a = 0); - var b = this.getInputData(1); - null == b && (b = 0); - var c = this.properties.f, d = this.getInputData(2); - void 0 !== d && (c = d); - this.setOutputData(0, a * (1 - c) + b * c); - }; - r.prototype.onGetInputs = function() { - return [["f", "number"]]; - }; - x.registerNodeType("math/lerp", r); - v.title = "Abs"; - v.desc = "Absolute"; - v.prototype.onExecute = function() { - var a = this.getInputData(0); - null != a && this.setOutputData(0, Math.abs(a)); - }; - x.registerNodeType("math/abs", v); - w.title = "Floor"; - w.desc = "Floor number to remove fractional part"; - w.prototype.onExecute = function() { - var a = this.getInputData(0); - null != a && this.setOutputData(0, Math.floor(a)); - }; - x.registerNodeType("math/floor", w); - l.title = "Frac"; - l.desc = "Returns fractional part"; - l.prototype.onExecute = function() { - var a = this.getInputData(0); - null != a && this.setOutputData(0, a % 1); - }; - x.registerNodeType("math/frac", l); - d.title = "Smoothstep"; - d.desc = "Smoothstep"; - d.prototype.onExecute = function() { - var a = this.getInputData(0); - if (void 0 !== a) { - var b = this.properties.A; - a = Math.clamp((a - b) / (this.properties.B - b), 0.0, 1.0); - this.setOutputData(0, a * a * (3 - 2 * a)); - } - }; - x.registerNodeType("math/smoothstep", d); - c.title = "Scale"; - c.desc = "v * factor"; - c.prototype.onExecute = function() { - var a = this.getInputData(0); - null != a && this.setOutputData(0, a * this.properties.factor); - }; - x.registerNodeType("math/scale", c); - a.title = "Average"; - a.desc = "Average Filter"; - a.prototype.onExecute = function() { - var a = this.getInputData(0); - null == a && (a = 0); - var b = this._values.length; - this._values[this._current % b] = a; - this._current += 1; - this._current > b && (this._current = 0); - for (var c = a = 0; c < b; ++c) { - a += this._values[c]; - } - this.setOutputData(0, a / b); - }; - a.prototype.onPropertyChanged = function(a, b) { - 1 > b && (b = 1); - this.properties.samples = Math.round(b); - a = this._values; - this._values = new Float32Array(this.properties.samples); - a.length <= this._values.length ? this._values.set(a) : this._values.set(a.subarray(0, this._values.length)); - }; - x.registerNodeType("math/average", a); - b.title = "TendTo"; - b.desc = "moves the output value always closer to the input"; - b.prototype.onExecute = function() { - var a = this.getInputData(0); - null == a && (a = 0); - var b = this.properties.factor; - this._value = null == this._value ? a : this._value * (1 - b) + a * b; - this.setOutputData(0, this._value); - }; - x.registerNodeType("math/tendTo", b); - e.values = "+-*/%^".split(""); - e.title = "Operation"; - e.desc = "Easy math operators"; - e["@OP"] = {type:"enum", title:"operation", values:e.values}; - e.prototype.getTitle = function() { - return "A " + this.properties.OP + " B"; - }; - e.prototype.setValue = function(a) { - "string" == typeof a && (a = parseFloat(a)); - this.properties.value = a; - }; - e.prototype.onExecute = function() { - var a = this.getInputData(0), b = this.getInputData(1); - null != a ? this.properties.A = a : a = this.properties.A; - null != b ? this.properties.B = b : b = this.properties.B; - var c = 0; - switch(this.properties.OP) { - case "+": - c = a + b; - break; - case "-": - c = a - b; - break; - case "x": - case "X": - case "*": - c = a * b; - break; - case "/": - c = a / b; - break; - case "%": - c = a % b; - break; - case "^": - c = Math.pow(a, b); - break; - default: - console.warn("Unknown operation: " + this.properties.OP); - } - this.setOutputData(0, c); - }; - e.prototype.onDrawBackground = function(a) { - this.flags.collapsed || (a.font = "40px Arial", a.fillStyle = "#CCC", a.textAlign = "center", a.fillText(this.properties.OP, 0.5 * this.size[0], 0.35 * this.size[1] + x.NODE_TITLE_HEIGHT), a.textAlign = "left"); - }; - x.registerNodeType("math/operation", e); - y.title = "Compare"; - y.desc = "compares between two values"; - y.prototype.onExecute = function() { - var a = this.getInputData(0), b = this.getInputData(1); - void 0 !== a ? this.properties.A = a : a = this.properties.A; - void 0 !== b ? this.properties.B = b : b = this.properties.B; - for (var c = 0, d = this.outputs.length; c < d; ++c) { - var e = this.outputs[c]; - if (e.links && e.links.length) { - switch(e.name) { - case "A==B": - value = a == b; - break; - case "A!=B": - value = a != b; - break; - case "A>B": - value = a > b; - break; - case "A=B": - value = a >= b; - } - this.setOutputData(c, value); - } - } - }; - y.prototype.onGetOutputs = function() { - return [["A==B", "boolean"], ["A!=B", "boolean"], ["A>B", "boolean"], ["A=B", "boolean"], ["A<=B", "boolean"]]; - }; - x.registerNodeType("math/compare", y); - h.values = "> < == != <= >=".split(" "); - h["@OP"] = {type:"enum", title:"operation", values:h.values}; - h.title = "Condition"; - h.desc = "evaluates condition between A and B"; - h.prototype.onExecute = function() { - var a = this.getInputData(0); - void 0 === a ? a = this.properties.A : this.properties.A = a; - var b = this.getInputData(1); - void 0 === b ? b = this.properties.B : this.properties.B = b; - var c = !0; - switch(this.properties.OP) { - case ">": - c = a > b; - break; - case "<": - c = a < b; - break; - case "==": - c = a == b; - break; - case "!=": - c = a != b; - break; - case "<=": - c = a <= b; - break; - case ">=": - c = a >= b; - } - this.setOutputData(0, c); - }; - x.registerNodeType("math/condition", h); - m.title = "Accumulate"; - m.desc = "Increments a value every time"; - m.prototype.onExecute = function() { - null === this.properties.value && (this.properties.value = 0); - var a = this.getInputData(0); - this.properties.value = null !== a ? this.properties.value + a : this.properties.value + this.properties.increment; - this.setOutputData(0, this.properties.value); - }; - x.registerNodeType("math/accumulate", m); - p.title = "Trigonometry"; - p.desc = "Sin Cos Tan"; - p.filter = "shader"; - p.prototype.onExecute = function() { - var a = this.getInputData(0); - null == a && (a = 0); - var b = this.properties.amplitude, c = this.findInputSlot("amplitude"); - -1 != c && (b = this.getInputData(c)); - var d = this.properties.offset; - c = this.findInputSlot("offset"); - -1 != c && (d = this.getInputData(c)); - c = 0; - for (var e = this.outputs.length; c < e; ++c) { - switch(this.outputs[c].name) { - case "sin": - value = Math.sin(a); - break; - case "cos": - value = Math.cos(a); - break; - case "tan": - value = Math.tan(a); - break; - case "asin": - value = Math.asin(a); - break; - case "acos": - value = Math.acos(a); - break; - case "atan": - value = Math.atan(a); - } - this.setOutputData(c, b * value + d); - } - }; - p.prototype.onGetInputs = function() { - return [["v", "number"], ["amplitude", "number"], ["offset", "number"]]; - }; - p.prototype.onGetOutputs = function() { - return [["sin", "number"], ["cos", "number"], ["tan", "number"], ["asin", "number"], ["acos", "number"], ["atan", "number"]]; - }; - x.registerNodeType("math/trigonometry", p); - var z = function() { - this.addInputs("x", "number"); - this.addInputs("y", "number"); - this.addOutputs("", "number"); - this.properties = {x:1.0, y:1.0, formula:"x+y"}; - }; - z.title = "Formula"; - z.desc = "Compute safe formula"; - z.prototype.onExecute = function() { - var a = this.getInputData(0), b = this.getInputData(1); - null != a ? this.properties.x = a : a = this.properties.x; - null != b ? this.properties.y = b : b = this.properties.y; - a = math.eval(this.properties.formula, {x:a, y:b, T:this.graph.globaltime}); - this.setOutputData(0, a); - }; - z.prototype.onDrawBackground = function() { - this.outputs[0].label = this.properties.formula; - }; - z.prototype.onGetOutputs = function() { - return [["A-B", "number"], ["A*B", "number"], ["A/B", "number"]]; - }; - x.registerNodeType("math/formula", z); - B.title = "Vec2->XY"; - B.desc = "vector 2 to components"; - B.prototype.onExecute = function() { - var a = this.getInputData(0); - null != a && (this.setOutputData(0, a[0]), this.setOutputData(1, a[1])); - }; - x.registerNodeType("math3d/vec2-to-xyz", B); - G.title = "XY->Vec2"; - G.desc = "components to vector2"; - G.prototype.onExecute = function() { - var a = this.getInputData(0); - null == a && (a = this.properties.x); - var b = this.getInputData(1); - null == b && (b = this.properties.y); - var c = this._data; - c[0] = a; - c[1] = b; - this.setOutputData(0, c); - }; - x.registerNodeType("math3d/xy-to-vec2", G); - D.title = "Vec3->XYZ"; - D.desc = "vector 3 to components"; - D.prototype.onExecute = function() { - var a = this.getInputData(0); - null != a && (this.setOutputData(0, a[0]), this.setOutputData(1, a[1]), this.setOutputData(2, a[2])); - }; - x.registerNodeType("math3d/vec3-to-xyz", D); - E.title = "XYZ->Vec3"; - E.desc = "components to vector3"; - E.prototype.onExecute = function() { - var a = this.getInputData(0); - null == a && (a = this.properties.x); - var b = this.getInputData(1); - null == b && (b = this.properties.y); - var c = this.getInputData(2); - null == c && (c = this.properties.z); - var d = this._data; - d[0] = a; - d[1] = b; - d[2] = c; - this.setOutputData(0, d); - }; - x.registerNodeType("math3d/xyz-to-vec3", E); - H.title = "Vec4->XYZW"; - H.desc = "vector 4 to components"; - H.prototype.onExecute = function() { - var a = this.getInputData(0); - null != a && (this.setOutputData(0, a[0]), this.setOutputData(1, a[1]), this.setOutputData(2, a[2]), this.setOutputData(3, a[3])); - }; - x.registerNodeType("math3d/vec4-to-xyzw", H); - A.title = "XYZW->Vec4"; - A.desc = "components to vector4"; - A.prototype.onExecute = function() { - var a = this.getInputData(0); - null == a && (a = this.properties.x); - var b = this.getInputData(1); - null == b && (b = this.properties.y); - var c = this.getInputData(2); - null == c && (c = this.properties.z); - var d = this.getInputData(3); - null == d && (d = this.properties.w); - var e = this._data; - e[0] = a; - e[1] = b; - e[2] = c; - e[3] = d; - this.setOutputData(0, e); - }; - x.registerNodeType("math3d/xyzw-to-vec4", A); - if (u.glMatrix) { - u = function() { - this.addInputs([["A", "quat"], ["B", "quat"], ["factor", "number"]]); - this.addOutput("slerp", "quat"); - this.addProperty("factor", 0.5); - this._value = quat.create(); - }; - z = function() { - this.addInputs([["A", "quat"], ["B", "quat"]]); - this.addOutput("A*B", "quat"); - this._value = quat.create(); - }; - var C = function() { - this.addInputs([["vec3", "vec3"], ["quat", "quat"]]); - this.addOutput("result", "vec3"); - this.properties = {vec:[0, 0, 1]}; - }, F = function() { - this.addInputs([["degrees", "number"], ["axis", "vec3"]]); - this.addOutput("quat", "quat"); - this.properties = {angle:90.0, axis:vec3.fromValues(0, 1, 0)}; - this._value = quat.create(); - }, I = function() { - this.addOutput("quat", "quat"); - this.properties = {x:0, y:0, z:0, w:1}; - this._value = quat.create(); - }; - I.title = "Quaternion"; - I.desc = "quaternion"; - I.prototype.onExecute = function() { - this._value[0] = this.properties.x; - this._value[1] = this.properties.y; - this._value[2] = this.properties.z; - this._value[3] = this.properties.w; - this.setOutputData(0, this._value); - }; - x.registerNodeType("math3d/quaternion", I); - F.title = "Rotation"; - F.desc = "quaternion rotation"; - F.prototype.onExecute = function() { - var a = this.getInputData(0); - null == a && (a = this.properties.angle); - var b = this.getInputData(1); - null == b && (b = this.properties.axis); - a = quat.setAxisAngle(this._value, b, 0.0174532925 * a); - this.setOutputData(0, a); - }; - x.registerNodeType("math3d/rotation", F); - C.title = "Rot. Vec3"; - C.desc = "rotate a point"; - C.prototype.onExecute = function() { - var a = this.getInputData(0); - null == a && (a = this.properties.vec); - var b = this.getInputData(1); - null == b ? this.setOutputData(a) : this.setOutputData(0, vec3.transformQuat(vec3.create(), a, b)); - }; - x.registerNodeType("math3d/rotate_vec3", C); - z.title = "Mult. Quat"; - z.desc = "rotate quaternion"; - z.prototype.onExecute = function() { - var a = this.getInputData(0); - if (null != a) { - var b = this.getInputData(1); - null != b && (a = quat.multiply(this._value, a, b), this.setOutputData(0, a)); - } - }; - x.registerNodeType("math3d/mult-quat", z); - u.title = "Quat Slerp"; - u.desc = "quaternion spherical interpolation"; - u.prototype.onExecute = function() { - var a = this.getInputData(0); - if (null != a) { - var b = this.getInputData(1); - if (null != b) { - var c = this.properties.factor; - null != this.getInputData(2) && (c = this.getInputData(2)); - a = quat.slerp(this._value, a, b, c); - this.setOutputData(0, a); - } - } - }; - x.registerNodeType("math3d/quat-slerp", u); - } -})(this); -(function(u) { - function f() { - this.addInput("sel", "boolean"); - this.addOutput("value", "number"); - this.properties = {A:0, B:1}; - this.size = [60, 20]; - } - u = u.LiteGraph; - f.title = "Selector"; - f.desc = "outputs A if selector is true, B if selector is false"; - f.prototype.onExecute = function() { - var f = this.getInputData(0); - if (void 0 !== f) { - for (var n = 1; n < this.inputs.length; n++) { - var g = this.inputs[n], q = this.getInputData(n); - void 0 !== q && (this.properties[g.name] = q); - } - n = this.properties.A; - g = this.properties.B; - this.setOutputData(0, f ? n : g); - } - }; - f.prototype.onGetInputs = function() { - return [["A", 0], ["B", 0]]; - }; - u.registerNodeType("logic/selector", f); -})(this); -(function(u) { - function f() { - this.addInput("A", "Number"); - this.addInput("B", "Number"); - this.addInput("C", "Number"); - this.addInput("D", "Number"); - this.values = [[], [], [], []]; - this.properties = {scale:2}; - } - function k() { - this.addOutput("frame", "image"); - this.properties = {url:""}; - } - function n() { - this.addInput("f", "number"); - this.addOutput("Color", "color"); - this.properties = {colorA:"#444444", colorB:"#44AAFF", colorC:"#44FFAA", colorD:"#FFFFFF"}; - } - function g() { - this.addInput("", "image"); - this.size = [200, 200]; - } - function q() { - this.addInputs([["img1", "image"], ["img2", "image"], ["fade", "number"]]); - this.addOutput("", "image"); - this.properties = {fade:0.5, width:512, height:512}; - } - function r() { - this.addInput("", "image"); - this.addOutput("", "image"); - this.properties = {width:256, height:256, x:0, y:0, scale:1.0}; - this.size = [50, 20]; - } - function v() { - this.addInput("t", "number"); - this.addOutputs([["frame", "image"], ["t", "number"], ["d", "number"]]); - this.properties = {url:"", use_proxy:!0}; - } - function w() { - this.addOutput("Webcam", "image"); - this.properties = {}; - } - var l = u.LiteGraph; - f.title = "Plot"; - f.desc = "Plots data over time"; - f.colors = ["#FFF", "#F99", "#9F9", "#99F"]; - f.prototype.onExecute = function(d) { - if (!this.flags.collapsed) { - d = this.size; - for (var c = 0; 4 > c; ++c) { - var a = this.getInputData(c); - if (null != a) { - var b = this.values[c]; - b.push(a); - b.length > d[0] && b.shift(); - } - } - } - }; - f.prototype.onDrawBackground = function(d) { - if (!this.flags.collapsed) { - var c = this.size, a = 0.5 * c[1] / this.properties.scale, b = f.colors, e = 0.5 * c[1]; - d.fillStyle = "#000"; - d.fillRect(0, 0, c[0], c[1]); - d.strokeStyle = "#555"; - d.beginPath(); - d.moveTo(0, e); - d.lineTo(c[0], e); - d.stroke(); - for (var g = 0; 4 > g; ++g) { - var h = this.values[g]; - d.strokeStyle = b[g]; - d.beginPath(); - var m = h[0] * a * -1 + e; - d.moveTo(0, Math.clamp(m, 0, c[1])); - for (var k = 1; k < h.length && k < c[0]; ++k) { - m = h[k] * a * -1 + e, d.lineTo(k, Math.clamp(m, 0, c[1])); - } - d.stroke(); - } - } - }; - l.registerNodeType("graphics/plot", f); - k.title = "Image"; - k.desc = "Image loader"; - k.widgets = [{name:"load", text:"Load", type:"button"}]; - k.supported_extensions = ["jpg", "jpeg", "png", "gif"]; - k.prototype.onAdded = function() { - "" != this.properties.url && null == this.img && this.loadImage(this.properties.url); - }; - k.prototype.onDrawBackground = function(d) { - this.img && 5 < this.size[0] && 5 < this.size[1] && d.drawImage(this.img, 0, 0, this.size[0], this.size[1]); - }; - k.prototype.onExecute = function() { - this.img || (this.boxcolor = "#000"); - this.img && this.img.width ? this.setOutputData(0, this.img) : this.setOutputData(0, null); - this.img && this.img.dirty && (this.img.dirty = !1); - }; - k.prototype.onPropertyChanged = function(d, c) { - this.properties[d] = c; - "url" == d && "" != c && this.loadImage(c); - return !0; - }; - k.prototype.loadImage = function(d, c) { - if ("" == d) { - this.img = null; - } else { - this.img = document.createElement("img"); - "http" == d.substr(0, 4) && l.proxy && (d = l.proxy + d.substr(d.indexOf(":") + 3)); - this.img.src = d; - this.boxcolor = "#F95"; - var a = this; - this.img.onload = function() { - c && c(this); - a.trace("Image loaded, size: " + a.img.width + "x" + a.img.height); - this.dirty = !0; - a.boxcolor = "#9F9"; - a.setDirtyCanvas(!0); - }; - } - }; - k.prototype.onWidget = function(d, c) { - "load" == c.name && this.loadImage(this.properties.url); - }; - k.prototype.onDropFile = function(d) { - var c = this; - this._url && URL.revokeObjectURL(this._url); - this._url = URL.createObjectURL(d); - this.properties.url = this._url; - this.loadImage(this._url, function(a) { - c.size[1] = a.height / a.width * c.size[0]; - }); - }; - l.registerNodeType("graphics/image", k); - n.title = "Palette"; - n.desc = "Generates a color"; - n.prototype.onExecute = function() { - var d = []; - null != this.properties.colorA && d.push(hex2num(this.properties.colorA)); - null != this.properties.colorB && d.push(hex2num(this.properties.colorB)); - null != this.properties.colorC && d.push(hex2num(this.properties.colorC)); - null != this.properties.colorD && d.push(hex2num(this.properties.colorD)); - var c = this.getInputData(0); - null == c && (c = 0.5); - 1.0 < c ? c = 1.0 : 0.0 > c && (c = 0.0); - if (0 != d.length) { - var a = [0, 0, 0]; - if (0 == c) { - a = d[0]; - } else { - if (1 == c) { - a = d[d.length - 1]; - } else { - var b = (d.length - 1) * c; - c = d[Math.floor(b)]; - d = d[Math.floor(b) + 1]; - b -= Math.floor(b); - a[0] = c[0] * (1 - b) + d[0] * b; - a[1] = c[1] * (1 - b) + d[1] * b; - a[2] = c[2] * (1 - b) + d[2] * b; - } - } - for (var e in a) { - a[e] /= 255; - } - this.boxcolor = colorToString(a); - this.setOutputData(0, a); - } - }; - l.registerNodeType("color/palette", n); - g.title = "Frame"; - g.desc = "Frame viewerew"; - g.widgets = [{name:"resize", text:"Resize box", type:"button"}, {name:"view", text:"View Image", type:"button"}]; - g.prototype.onDrawBackground = function(d) { - this.frame && d.drawImage(this.frame, 0, 0, this.size[0], this.size[1]); - }; - g.prototype.onExecute = function() { - this.frame = this.getInputData(0); - this.setDirtyCanvas(!0); - }; - g.prototype.onWidget = function(d, c) { - "resize" == c.name && this.frame ? (d = this.frame.width, c = this.frame.height, d || null == this.frame.videoWidth || (d = this.frame.videoWidth, c = this.frame.videoHeight), d && c && (this.size = [d, c]), this.setDirtyCanvas(!0, !0)) : "view" == c.name && this.show(); - }; - g.prototype.show = function() { - showElement && this.frame && showElement(this.frame); - }; - l.registerNodeType("graphics/frame", g); - q.title = "Image fade"; - q.desc = "Fades between images"; - q.widgets = [{name:"resizeA", text:"Resize to A", type:"button"}, {name:"resizeB", text:"Resize to B", type:"button"}]; - q.prototype.onAdded = function() { - this.createCanvas(); - var d = this.canvas.getContext("2d"); - d.fillStyle = "#000"; - d.fillRect(0, 0, this.properties.width, this.properties.height); - }; - q.prototype.createCanvas = function() { - this.canvas = document.createElement("canvas"); - this.canvas.width = this.properties.width; - this.canvas.height = this.properties.height; - }; - q.prototype.onExecute = function() { - var d = this.canvas.getContext("2d"); - this.canvas.width = this.canvas.width; - var c = this.getInputData(0); - null != c && d.drawImage(c, 0, 0, this.canvas.width, this.canvas.height); - c = this.getInputData(2); - null == c ? c = this.properties.fade : this.properties.fade = c; - d.globalAlpha = c; - c = this.getInputData(1); - null != c && d.drawImage(c, 0, 0, this.canvas.width, this.canvas.height); - d.globalAlpha = 1.0; - this.setOutputData(0, this.canvas); - this.setDirtyCanvas(!0); - }; - l.registerNodeType("graphics/imagefade", q); - r.title = "Crop"; - r.desc = "Crop Image"; - r.prototype.onAdded = function() { - this.createCanvas(); - }; - r.prototype.createCanvas = function() { - this.canvas = document.createElement("canvas"); - this.canvas.width = this.properties.width; - this.canvas.height = this.properties.height; - }; - r.prototype.onExecute = function() { - var d = this.getInputData(0); - d && (d.width ? (this.canvas.getContext("2d").drawImage(d, -this.properties.x, -this.properties.y, d.width * this.properties.scale, d.height * this.properties.scale), this.setOutputData(0, this.canvas)) : this.setOutputData(0, null)); - }; - r.prototype.onDrawBackground = function(d) { - this.flags.collapsed || this.canvas && d.drawImage(this.canvas, 0, 0, this.canvas.width, this.canvas.height, 0, 0, this.size[0], this.size[1]); - }; - r.prototype.onPropertyChanged = function(d, c) { - this.properties[d] = c; - "scale" == d ? (this.properties[d] = parseFloat(c), 0 == this.properties[d] && (this.trace("Error in scale"), this.properties[d] = 1.0)) : this.properties[d] = parseInt(c); - this.createCanvas(); - return !0; - }; - l.registerNodeType("graphics/cropImage", r); - v.title = "Video"; - v.desc = "Video playback"; - v.widgets = [{name:"play", text:"PLAY", type:"minibutton"}, {name:"stop", text:"STOP", type:"minibutton"}, {name:"demo", text:"Demo video", type:"button"}, {name:"mute", text:"Mute video", type:"button"}]; - v.prototype.onExecute = function() { - if (this.properties.url && (this.properties.url != this._video_url && this.loadVideo(this.properties.url), this._video && 0 != this._video.width)) { - var d = this.getInputData(0); - d && 0 <= d && 1.0 >= d && (this._video.currentTime = d * this._video.duration, this._video.pause()); - this._video.dirty = !0; - this.setOutputData(0, this._video); - this.setOutputData(1, this._video.currentTime); - this.setOutputData(2, this._video.duration); - this.setDirtyCanvas(!0); - } - }; - v.prototype.onStart = function() { - this.play(); - }; - v.prototype.onStop = function() { - this.stop(); - }; - v.prototype.loadVideo = function(d) { - this._video_url = d; - this.properties.use_proxy && "http" == d.substr(0, 4) && l.proxy && (d = l.proxy + d.substr(d.indexOf(":") + 3)); - this._video = document.createElement("video"); - this._video.src = d; - this._video.type = "type=video/mp4"; - this._video.muted = !0; - this._video.autoplay = !0; - var c = this; - this._video.addEventListener("loadedmetadata", function(a) { - c.trace("Duration: " + this.duration + " seconds"); - c.trace("Size: " + this.videoWidth + "," + this.videoHeight); - c.setDirtyCanvas(!0); - this.width = this.videoWidth; - this.height = this.videoHeight; - }); - this._video.addEventListener("progress", function(a) { - }); - this._video.addEventListener("error", function(a) { - console.log("Error loading video: " + this.src); - c.trace("Error loading video: " + this.src); - if (this.error) { - switch(this.error.code) { - case this.error.MEDIA_ERR_ABORTED: - c.trace("You stopped the video."); - break; - case this.error.MEDIA_ERR_NETWORK: - c.trace("Network error - please try again later."); - break; - case this.error.MEDIA_ERR_DECODE: - c.trace("Video is broken.."); - break; - case this.error.MEDIA_ERR_SRC_NOT_SUPPORTED: - c.trace("Sorry, your browser can't play this video."); - } - } - }); - this._video.addEventListener("ended", function(a) { - c.trace("Ended."); - this.play(); - }); - }; - v.prototype.onPropertyChanged = function(d, c) { - this.properties[d] = c; - "url" == d && "" != c && this.loadVideo(c); - return !0; - }; - v.prototype.play = function() { - this._video && this._video.play(); - }; - v.prototype.playPause = function() { - this._video && (this._video.paused ? this.play() : this.pause()); - }; - v.prototype.stop = function() { - this._video && (this._video.pause(), this._video.currentTime = 0); - }; - v.prototype.pause = function() { - this._video && (this.trace("Video paused"), this._video.pause()); - }; - v.prototype.onWidget = function(d, c) { - }; - l.registerNodeType("graphics/video", v); - w.title = "Webcam"; - w.desc = "Webcam image"; - w.prototype.openStream = function() { - navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia; - window.URL = window.URL || window.webkitURL; - if (navigator.getUserMedia) { - this._waiting_confirmation = !0; - navigator.getUserMedia({video:!0}, this.streamReady.bind(this), function(c) { - console.log("Webcam rejected", c); - d._webcam_stream = !1; - d.box_color = "red"; - }); - var d = this; - } - }; - w.prototype.onRemoved = function() { - this._webcam_stream && (this._webcam_stream.stop(), this._video = this._webcam_stream = null); - }; - w.prototype.streamReady = function(d) { - this._webcam_stream = d; - var c = this._video; - c || (c = document.createElement("video"), c.autoplay = !0, c.src = window.URL.createObjectURL(d), this._video = c, c.onloadedmetadata = function(a) { - console.log(a); - }); - }; - w.prototype.onExecute = function() { - null != this._webcam_stream || this._waiting_confirmation || this.openStream(); - this._video && this._video.videoWidth && (this._video.width = this._video.videoWidth, this._video.height = this._video.videoHeight, this.setOutputData(0, this._video)); - }; - w.prototype.getExtraMenuOptions = function(d) { - var c = this; - return [{content:c.properties.show ? "Hide Frame" : "Show Frame", callback:function() { - c.properties.show = !c.properties.show; - }}]; - }; - w.prototype.onDrawBackground = function(d) { - this.flags.collapsed || 20 >= this.size[1] || !this.properties.show || !this._video || (d.save(), d.drawImage(this._video, 0, 0, this.size[0], this.size[1]), d.restore()); - }; - l.registerNodeType("graphics/webcam", w); -})(this); -(function(u) { - var f = u.LiteGraph; - u.LGraphTexture = null; - if ("undefined" != typeof GL) { - var k = function() { - this.addOutput("Cubemap", "Cubemap"); - this.properties = {name:""}; - this.size = [t.image_preview_size, t.image_preview_size]; - }, n = function() { - this.addInput("in", "Texture"); - this.addOutput("out", "Texture"); - this.properties = {key_color:vec3.fromValues(0., 1., 0.), threshold:0.8, slope:0.2, precision:t.DEFAULT}; - }, g = function() { - this.addOutput("out", "Texture"); - this.properties = {code:"", width:512, height:512, precision:t.DEFAULT}; - this._temp_texture = this._func = null; - }, q = function() { - this.addOutput("out", "Texture"); - this.properties = {width:512, height:512, seed:0, persistence:0.1, octaves:8, scale:1, offset:[0, 0], amplitude:1, precision:t.DEFAULT}; - this._key = 0; - this._uniforms = {u_persistence:0.1, u_seed:0, u_offset:vec2.create(), u_scale:1, u_viewport:vec2.create()}; - }, r = function() { - this.addInput("in", "Texture"); - this.addInput("avg", "number"); - this.addOutput("out", "Texture"); - this.properties = {enabled:!0, scale:1, gamma:1, average_lum:1, lum_white:1, precision:t.LOW}; - this._uniforms = {u_texture:0, u_lumwhite2:1, u_igamma:1, u_scale:1, u_average_lum:1}; - }, v = function() { - this.addInput("in", "Texture"); - this.addInput("exp", "number"); - this.addOutput("out", "Texture"); - this.properties = {exposition:1, precision:t.LOW}; - this._uniforms = {u_texture:0, u_exposition:exp}; - }, w = function() { - this.addInput("in", "Texture"); - this.addInput("f", "number"); - this.addOutput("out", "Texture"); - this.properties = {enabled:!0, factor:1, precision:t.LOW}; - this._uniforms = {u_texture:0, u_factor:1}; - }, l = function() { - this.addOutput("Webcam", "Texture"); - this.properties = {texture_name:""}; - }, d = function() { - this.addInput("Texture", "Texture"); - this.addOutput("Filtered", "Texture"); - this.properties = {intensity:1, radius:5}; - }, c = function() { - this.addInput("in", "Texture"); - this.addInput("dirt", "Texture"); - this.addOutput("out", "Texture"); - this.addOutput("glow", "Texture"); - this.properties = {enabled:!0, intensity:1, persistence:0.99, iterations:16, threshold:0, scale:1, dirt_factor:0.5, precision:t.DEFAULT}; - this._textures = []; - this._uniforms = {u_intensity:1, u_texture:0, u_glow_texture:1, u_threshold:0, u_texel_size:vec2.create()}; - }, a = function() { - this.addInput("Texture", "Texture"); - this.addInput("Iterations", "number"); - this.addInput("Intensity", "number"); - this.addOutput("Blurred", "Texture"); - this.properties = {intensity:1, iterations:1, preserve_aspect:!1, scale:[1, 1], precision:t.DEFAULT}; - }, b = function() { - this.addInput("Texture", "Texture"); - this.addInput("Distance", "number"); - this.addInput("Range", "number"); - this.addOutput("Texture", "Texture"); - this.properties = {distance:100, range:50, only_depth:!1, high_precision:!1}; - this._uniforms = {u_texture:0, u_distance:100, u_range:50, u_camera_planes:null}; - }, e = function() { - this.addInput("Tex.", "Texture"); - this.addOutput("Edges", "Texture"); - this.properties = {invert:!0, threshold:!1, factor:1, precision:t.DEFAULT}; - e._shader || (e._shader = new GL.Shader(Shader.SCREEN_VERTEX_SHADER, e.pixel_shader)); - }, y = function() { - this.addInput("A", "Texture"); - this.addInput("B", "Texture"); - this.addInput("Mixer", "Texture"); - this.addOutput("Texture", "Texture"); - this.properties = {precision:t.DEFAULT}; - y._shader || (y._shader = new GL.Shader(Shader.SCREEN_VERTEX_SHADER, y.pixel_shader)); - }, h = function() { - this.addInput("A", "color"); - this.addInput("B", "color"); - this.addOutput("Texture", "Texture"); - this.properties = {angle:0, scale:1, A:[0, 0, 0], B:[1, 1, 1], texture_size:32}; - h._shader || (h._shader = new GL.Shader(Shader.SCREEN_VERTEX_SHADER, h.pixel_shader)); - this._uniforms = {u_angle:0, u_colorA:vec3.create(), u_colorB:vec3.create()}; - }, m = function() { - this.addInput("R", "Texture"); - this.addInput("G", "Texture"); - this.addInput("B", "Texture"); - this.addInput("A", "Texture"); - this.addOutput("Texture", "Texture"); - this.properties = {}; - m._shader || (m._shader = new GL.Shader(Shader.SCREEN_VERTEX_SHADER, m.pixel_shader)); - }, p = function() { - this.addInput("Texture", "Texture"); - this.addOutput("R", "Texture"); - this.addOutput("G", "Texture"); - this.addOutput("B", "Texture"); - this.addOutput("A", "Texture"); - this.properties = {}; - p._shader || (p._shader = new GL.Shader(Shader.SCREEN_VERTEX_SHADER, p.pixel_shader)); - }, B = function() { - this.addInput("Texture", "Texture"); - this.addInput("LUT", "Texture"); - this.addInput("Intensity", "number"); - this.addOutput("", "Texture"); - this.properties = {intensity:1, precision:t.DEFAULT, texture:null}; - B._shader || (B._shader = new GL.Shader(Shader.SCREEN_VERTEX_SHADER, B.pixel_shader)); - }, G = function() { - this.addInput("Image", "image"); - this.addOutput("", "Texture"); - this.properties = {}; - }, D = function() { - this.addInput("Texture", "Texture"); - this.addOutput("tex", "Texture"); - this.addOutput("avg", "vec4"); - this.addOutput("lum", "number"); - this.properties = {mipmap_offset:0, low_precision:!1}; - this._uniforms = {u_texture:0, u_mipmap_offset:this.properties.mipmap_offset}; - this._luminance = new Float32Array(4); - }, E = function() { - this.addInput("Texture", "Texture"); - this.addOutput("", "Texture"); - this.properties = {iterations:1, generate_mipmaps:!1, precision:t.DEFAULT}; - }, H = function() { - this.addInput("Texture", "Texture"); - this.addOutput("", "Texture"); - this.properties = {size:0, generate_mipmaps:!1, precision:t.DEFAULT}; - }, A = function() { - this.addInput("Texture", "Texture"); - this.properties = {additive:!1, antialiasing:!1, filter:!0, disable_alpha:!1, gamma:1.0}; - this.size[0] = 130; - }, x = function() { - this.addInput("in", "Texture"); - this.addInput("warp", "Texture"); - this.addInput("factor", "number"); - this.addOutput("out", "Texture"); - this.properties = {factor:0.01, precision:t.DEFAULT}; - }, z = function() { - this.addInput("in", "Texture"); - this.addInput("scale", "vec2"); - this.addInput("offset", "vec2"); - this.addOutput("out", "Texture"); - this.properties = {offset:vec2.fromValues(0, 0), scale:vec2.fromValues(1, 1), precision:t.DEFAULT}; - }, C = function() { - this.addOutput("out", "Texture"); - this.properties = {code:"", width:512, height:512, precision:t.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 = {in_texture:0, texSize:vec2.create(), time:0}; - }, F = function() { - this.addInput("Texture", "Texture"); - this.addInput("TextureB", "Texture"); - this.addInput("value", "number"); - this.addOutput("Texture", "Texture"); - this.help = "

pixelcode must be vec3

\r\n\t\t\t

uvcode must be vec2, is optional

\r\n\t\t\t

uv: tex. coords

color: texture

colorB: textureB

time: scene time

value: input value

"; - this.properties = {value:1, uvcode:"", pixelcode:"color + colorB * value", precision:t.DEFAULT}; - }, I = function() { - this.addInput("Texture", "Texture"); - this.addOutput("", "Texture"); - this.properties = {name:""}; - }, J = function() { - this.addInput("Texture", "Texture"); - this.properties = {flipY:!1}; - this.size = [t.image_preview_size, t.image_preview_size]; - }, t = function() { - this.addOutput("Texture", "Texture"); - this.properties = {name:"", filter:!0}; - this.size = [t.image_preview_size, t.image_preview_size]; - }; - u.LGraphTexture = t; - t.title = "Texture"; - t.desc = "Texture"; - t.widgets_info = {name:{widget:"texture"}, filter:{widget:"checkbox"}}; - t.loadTextureCallback = null; - t.image_preview_size = 256; - t.PASS_THROUGH = 1; - t.COPY = 2; - t.LOW = 3; - t.HIGH = 4; - t.REUSE = 5; - t.DEFAULT = 2; - t.MODE_VALUES = {"pass through":t.PASS_THROUGH, copy:t.COPY, low:t.LOW, high:t.HIGH, reuse:t.REUSE, "default":t.DEFAULT}; - t.getTexturesContainer = function() { - return gl.textures; - }; - t.loadTexture = function(a, b) { - b = b || {}; - var c = a; - "http://" == c.substr(0, 7) && f.proxy && (c = f.proxy + c.substr(7)); - return t.getTexturesContainer()[a] = GL.Texture.fromURL(c, b); - }; - t.getTexture = function(a) { - var b = this.getTexturesContainer(); - if (!b) { - throw "Cannot load texture, container of textures not found"; - } - b = b[a]; - return !b && a && ":" != a[0] ? this.loadTexture(a) : b; - }; - t.getTargetTexture = function(a, b, c) { - if (!a) { - throw "LGraphTexture.getTargetTexture expects a reference texture"; - } - switch(c) { - case t.LOW: - c = gl.UNSIGNED_BYTE; - break; - case t.HIGH: - c = gl.HIGH_PRECISION_FORMAT; - break; - case t.REUSE: - return a; - default: - c = a ? a.type : gl.UNSIGNED_BYTE; - } - b && b.width == a.width && b.height == a.height && b.type == c || (b = new GL.Texture(a.width, a.height, {type:c, format:gl.RGBA, filter:gl.LINEAR})); - return b; - }; - t.getTextureType = function(a, b) { - b = b ? b.type : gl.UNSIGNED_BYTE; - switch(a) { - case t.HIGH: - b = gl.HIGH_PRECISION_FORMAT; - break; - case t.LOW: - b = gl.UNSIGNED_BYTE; - } - return b; - }; - t.getNoiseTexture = function() { - if (this._noise_texture) { - return this._noise_texture; - } - for (var a = new Uint8Array(1048576), b = 0; 1048576 > b; ++b) { - a[b] = 255 * Math.random(); - } - return this._noise_texture = a = GL.Texture.fromMemory(512, 512, a, {format:gl.RGBA, wrap:gl.REPEAT, filter:gl.NEAREST}); - }; - t.prototype.onDropFile = function(a, b, c) { - a ? ("string" == typeof a ? a = GL.Texture.fromURL(a) : -1 != b.toLowerCase().indexOf(".dds") ? a = GL.Texture.fromDDSInMemory(a) : (a = new Blob([c]), a = URL.createObjectURL(a), a = GL.Texture.fromURL(a)), this._drop_texture = a, this.properties.name = b) : (this._drop_texture = null, this.properties.name = ""); - }; - t.prototype.getExtraMenuOptions = function(a) { - var b = this; - if (this._drop_texture) { - return [{content:"Clear", callback:function() { - b._drop_texture = null; - b.properties.name = ""; - }}]; - } - }; - t.prototype.onExecute = function() { - var a = null; - this.isOutputConnected(1) && (a = this.getInputData(0)); - !a && this._drop_texture && (a = this._drop_texture); - !a && this.properties.name && (a = t.getTexture(this.properties.name)); - if (a) { - this._last_tex = a; - !1 === this.properties.filter ? a.setParameter(gl.TEXTURE_MAG_FILTER, gl.NEAREST) : a.setParameter(gl.TEXTURE_MAG_FILTER, gl.LINEAR); - this.setOutputData(0, a); - for (var b = 1; b < this.outputs.length; b++) { - var c = this.outputs[b]; - if (c) { - var d = null; - "width" == c.name ? d = a.width : "height" == c.name ? d = a.height : "aspect" == c.name && (d = a.width / a.height); - this.setOutputData(b, d); - } - } - } - }; - t.prototype.onResourceRenamed = function(a, b) { - this.properties.name == a && (this.properties.name = b); - }; - t.prototype.onDrawBackground = function(a) { - if (!(this.flags.collapsed || 20 >= this.size[1])) { - if (this._drop_texture && a.webgl) { - a.drawImage(this._drop_texture, 0, 0, this.size[0], this.size[1]); - } else { - if (this._last_preview_tex != this._last_tex) { - if (a.webgl) { - this._canvas = this._last_tex; - } else { - var b = t.generateLowResTexturePreview(this._last_tex); - if (!b) { - return; - } - this._last_preview_tex = this._last_tex; - this._canvas = cloneCanvas(b); - } - } - this._canvas && (a.save(), a.webgl || (a.translate(0, this.size[1]), a.scale(1, -1)), a.drawImage(this._canvas, 0, 0, this.size[0], this.size[1]), a.restore()); - } - } - }; - t.generateLowResTexturePreview = function(a) { - if (!a) { - return null; - } - var b = t.image_preview_size, c = a; - if (a.format == gl.DEPTH_COMPONENT) { - return null; - } - if (a.width > b || a.height > b) { - c = this._preview_temp_tex, this._preview_temp_tex || (this._preview_temp_tex = c = new GL.Texture(b, b, {minFilter:gl.NEAREST})), a.copyTo(c); - } - a = this._preview_canvas; - a || (this._preview_canvas = a = createCanvas(b, b)); - c && c.toCanvas(a); - return a; - }; - t.prototype.getResources = function(a) { - a[this.properties.name] = GL.Texture; - return a; - }; - t.prototype.onGetInputs = function() { - return [["in", "Texture"]]; - }; - t.prototype.onGetOutputs = function() { - return [["width", "number"], ["height", "number"], ["aspect", "number"]]; - }; - f.registerNodeType("texture/texture", t); - J.title = "Preview"; - J.desc = "Show a texture in the graph canvas"; - J.allow_preview = !1; - J.prototype.onDrawBackground = function(a) { - if (!this.flags.collapsed && (a.webgl || J.allow_preview)) { - var b = this.getInputData(0); - b && (b = !b.handle && a.webgl ? b : t.generateLowResTexturePreview(b), a.save(), this.properties.flipY && (a.translate(0, this.size[1]), a.scale(1, -1)), a.drawImage(b, 0, 0, this.size[0], this.size[1]), a.restore()); - } - }; - f.registerNodeType("texture/preview", J); - I.title = "Save"; - I.desc = "Save a texture in the repository"; - I.prototype.onExecute = function() { - var a = this.getInputData(0); - a && (this.properties.name && (t.storeTexture ? t.storeTexture(this.properties.name, a) : t.getTexturesContainer()[this.properties.name] = a), this.setOutputData(0, a)); - }; - f.registerNodeType("texture/save", I); - F.widgets_info = {uvcode:{widget:"textarea", height:100}, pixelcode:{widget:"textarea", height:100}, precision:{widget:"combo", values:t.MODE_VALUES}}; - F.title = "Operation"; - F.desc = "Texture shader operation"; - F.prototype.getExtraMenuOptions = function(a) { - var b = this; - return [{content:b.properties.show ? "Hide Texture" : "Show Texture", callback:function() { - b.properties.show = !b.properties.show; - }}]; - }; - F.prototype.onDrawBackground = function(a) { - this.flags.collapsed || 20 >= this.size[1] || !this.properties.show || !this._tex || this._tex.gl != a || (a.save(), a.drawImage(this._tex, 0, 0, this.size[0], this.size[1]), a.restore()); - }; - F.prototype.onExecute = function() { - var a = this.getInputData(0); - if (this.isOutputConnected(0)) { - if (this.properties.precision === t.PASS_THROUGH) { - this.setOutputData(0, a); - } else { - var b = this.getInputData(1); - if (this.properties.uvcode || this.properties.pixelcode) { - var c = 512, d = 512; - a ? (c = a.width, d = a.height) : b && (c = b.width, d = b.height); - var e = t.getTextureType(this.properties.precision, a); - this._tex = a || this._tex ? t.getTargetTexture(a || this._tex, this._tex, this.properties.precision) : new GL.Texture(c, d, {type:e, format:gl.RGBA, filter:gl.LINEAR}); - e = ""; - this.properties.uvcode && (e = "uv = " + this.properties.uvcode, -1 != this.properties.uvcode.indexOf(";") && (e = this.properties.uvcode)); - var g = ""; - this.properties.pixelcode && (g = "result = " + this.properties.pixelcode, -1 != this.properties.pixelcode.indexOf(";") && (g = this.properties.pixelcode)); - var f = this._shader; - if (!f || this._shader_code != e + "|" + g) { - try { - this._shader = new GL.Shader(Shader.SCREEN_VERTEX_SHADER, F.pixel_shader, {UV_CODE:e, PIXEL_CODE:g}), this.boxcolor = "#00FF00"; - } catch (N) { - console.log("Error compiling shader: ", N); - this.boxcolor = "#FF0000"; - return; - } - this.boxcolor = "#FF0000"; - this._shader_code = e + "|" + g; - f = this._shader; - } - if (f) { - this.boxcolor = "green"; - var h = this.getInputData(2); - null != h ? this.properties.value = h : h = parseFloat(this.properties.value); - var m = this.graph.getTime(); - this._tex.drawTo(function() { - gl.disable(gl.DEPTH_TEST); - gl.disable(gl.CULL_FACE); - gl.disable(gl.BLEND); - a && a.bind(0); - b && b.bind(1); - var e = Mesh.getScreenQuad(); - f.uniforms({u_texture:0, u_textureB:1, value:h, texSize:[c, d], time:m}).draw(e); - }); - this.setOutputData(0, this._tex); - } else { - this.boxcolor = "red"; - } - } - } - } - }; - F.pixel_shader = "precision highp float;\n\r\n\t\t\t\n\r\n\t\t\tuniform sampler2D u_texture;\n\r\n\t\t\tuniform sampler2D u_textureB;\n\r\n\t\t\tvarying vec2 v_coord;\n\r\n\t\t\tuniform vec2 texSize;\n\r\n\t\t\tuniform float time;\n\r\n\t\t\tuniform float value;\n\r\n\t\t\t\n\r\n\t\t\tvoid main() {\n\r\n\t\t\t\tvec2 uv = v_coord;\n\r\n\t\t\t\tUV_CODE;\n\r\n\t\t\t\tvec4 color4 = texture2D(u_texture, uv);\n\r\n\t\t\t\tvec3 color = color4.rgb;\n\r\n\t\t\t\tvec4 color4B = texture2D(u_textureB, uv);\n\r\n\t\t\t\tvec3 colorB = color4B.rgb;\n\r\n\t\t\t\tvec3 result = color;\n\r\n\t\t\t\tfloat alpha = 1.0;\n\r\n\t\t\t\tPIXEL_CODE;\n\r\n\t\t\t\tgl_FragColor = vec4(result, alpha);\n\r\n\t\t\t}\n\r\n\t\t\t"; - f.registerNodeType("texture/operation", F); - C.title = "Shader"; - C.desc = "Texture shader"; - C.widgets_info = {code:{type:"code"}, precision:{widget:"combo", values:t.MODE_VALUES}}; - C.prototype.onPropertyChanged = function(a, b) { - if ("code" == a && (a = this.getShader())) { - b = a.uniformInfo; - if (this.inputs) { - for (var c = {}, d = 0; d < this.inputs.length; ++d) { - var e = this.getInputInfo(d); - e && (b[e.name] && !c[e.name] ? c[e.name] = !0 : (this.removeInput(d), d--)); - } - } - for (d in b) { - if (e = a.uniformInfo[d], null !== e.loc && "time" != d) { - if (this._shader.samplers[d]) { - b = "texture"; - } else { - switch(e.size) { - case 1: - b = "number"; - break; - case 2: - b = "vec2"; - break; - case 3: - b = "vec3"; - break; - case 4: - b = "vec4"; - break; - case 9: - b = "mat3"; - break; - case 16: - b = "mat4"; - break; - default: - continue; - } - } - c = this.findInputSlot(d); - if (-1 != c && (e = this.getInputInfo(c))) { - if (e.type == b) { - continue; - } - this.removeInput(c, b); - } - this.addInput(d, b); - } - } - } - }; - C.prototype.getShader = function() { - if (this._shader && this._shader_code == this.properties.code) { - return this._shader; - } - this._shader_code = this.properties.code; - this._shader = new GL.Shader(Shader.SCREEN_VERTEX_SHADER, C.pixel_shader + this.properties.code), this.boxcolor = "green"; - return this._shader; - }; - C.prototype.onExecute = function() { - if (this.isOutputConnected(0)) { - var a = this.getShader(); - if (a) { - for (var b = 0, c = null, d = 0; d < this.inputs.length; ++d) { - var e = this.getInputInfo(d), g = this.getInputData(d); - null != g && (g.constructor === GL.Texture && (g.bind(b), c || (c = g), g = b, b++), a.setUniform(e.name, g)); - } - var f = this._uniforms; - b = t.getTextureType(this.properties.precision, c); - d = this.properties.width | 0; - e = this.properties.height | 0; - 0 == d && (d = c ? c.width : gl.canvas.width); - 0 == e && (e = c ? c.height : gl.canvas.height); - f.texSize[0] = d; - f.texSize[1] = e; - f.time = this.graph.getTime(); - this._tex && this._tex.type == b && this._tex.width == d && this._tex.height == e || (this._tex = new GL.Texture(d, e, {type:b, format:gl.RGBA, filter:gl.LINEAR})); - this._tex.drawTo(function() { - a.uniforms(f).draw(GL.Mesh.getScreenQuad()); - }); - this.setOutputData(0, this._tex); - } - } - }; - C.pixel_shader = "precision highp float;\n\r\n\t\t\t\n\r\n\t\t\tvarying vec2 v_coord;\n\r\n\t\t\tuniform float time;\n\r\n\t"; - f.registerNodeType("texture/shader", C); - z.widgets_info = {precision:{widget:"combo", values:t.MODE_VALUES}}; - z.title = "Scale/Offset"; - z.desc = "Applies an scaling and offseting"; - z.prototype.onExecute = function() { - var a = this.getInputData(0); - if (this.isOutputConnected(0) && a) { - if (this.properties.precision === t.PASS_THROUGH) { - this.setOutputData(0, a); - } else { - var b = a.width, c = a.height, d = this.precision === t.LOW ? gl.UNSIGNED_BYTE : gl.HIGH_PRECISION_FORMAT; - this.precision === t.DEFAULT && (d = a.type); - this._tex && this._tex.width == b && this._tex.height == c && this._tex.type == d || (this._tex = new GL.Texture(b, c, {type:d, format:gl.RGBA, filter:gl.LINEAR})); - var e = this._shader; - e || (e = new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER, z.pixel_shader)); - var g = this.getInputData(1); - g ? (this.properties.scale[0] = g[0], this.properties.scale[1] = g[1]) : g = this.properties.scale; - var f = this.getInputData(2); - f ? (this.properties.offset[0] = f[0], this.properties.offset[1] = f[1]) : f = this.properties.offset; - this._tex.drawTo(function() { - gl.disable(gl.DEPTH_TEST); - gl.disable(gl.CULL_FACE); - gl.disable(gl.BLEND); - a.bind(0); - var b = Mesh.getScreenQuad(); - e.uniforms({u_texture:0, u_scale:g, u_offset:f}).draw(b); - }); - this.setOutputData(0, this._tex); - } - } - }; - z.pixel_shader = "precision highp float;\n\r\n\t\t\t\n\r\n\t\t\tuniform sampler2D u_texture;\n\r\n\t\t\tuniform sampler2D u_textureB;\n\r\n\t\t\tvarying vec2 v_coord;\n\r\n\t\t\tuniform vec2 u_scale;\n\r\n\t\t\tuniform vec2 u_offset;\n\r\n\t\t\t\n\r\n\t\t\tvoid main() {\n\r\n\t\t\t\tvec2 uv = v_coord;\n\r\n\t\t\t\tuv = uv / u_scale - u_offset;\n\r\n\t\t\t\tgl_FragColor = texture2D(u_texture, uv);\n\r\n\t\t\t}\n\r\n\t\t\t"; - f.registerNodeType("texture/scaleOffset", z); - x.widgets_info = {precision:{widget:"combo", values:t.MODE_VALUES}}; - x.title = "Warp"; - x.desc = "Texture warp operation"; - x.prototype.onExecute = function() { - var a = this.getInputData(0); - if (this.isOutputConnected(0)) { - if (this.properties.precision === t.PASS_THROUGH) { - this.setOutputData(0, a); - } else { - var b = this.getInputData(1), c = 512, d = 512; - a ? (c = a.width, d = a.height) : b && (c = b.width, d = b.height); - this._tex = a || this._tex ? t.getTargetTexture(a || this._tex, this._tex, this.properties.precision) : new GL.Texture(c, d, {type:this.precision === t.LOW ? gl.UNSIGNED_BYTE : gl.HIGH_PRECISION_FORMAT, format:gl.RGBA, filter:gl.LINEAR}); - var e = this._shader; - e || (e = new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER, x.pixel_shader)); - var g = this.getInputData(2); - null != g ? this.properties.factor = g : g = parseFloat(this.properties.factor); - this._tex.drawTo(function() { - gl.disable(gl.DEPTH_TEST); - gl.disable(gl.CULL_FACE); - gl.disable(gl.BLEND); - a && a.bind(0); - b && b.bind(1); - var c = Mesh.getScreenQuad(); - e.uniforms({u_texture:0, u_textureB:1, u_factor:g}).draw(c); - }); - this.setOutputData(0, this._tex); - } - } - }; - x.pixel_shader = "precision highp float;\n\r\n\t\t\t\n\r\n\t\t\tuniform sampler2D u_texture;\n\r\n\t\t\tuniform sampler2D u_textureB;\n\r\n\t\t\tvarying vec2 v_coord;\n\r\n\t\t\tuniform float u_factor;\n\r\n\t\t\t\n\r\n\t\t\tvoid main() {\n\r\n\t\t\t\tvec2 uv = v_coord;\n\r\n\t\t\t\tuv += ( texture2D(u_textureB, uv).rg - vec2(0.5)) * u_factor;\n\r\n\t\t\t\tgl_FragColor = texture2D(u_texture, uv);\n\r\n\t\t\t}\n\r\n\t\t\t"; - f.registerNodeType("texture/warp", x); - A.title = "to Viewport"; - A.desc = "Texture to viewport"; - A.prototype.onExecute = function() { - var a = this.getInputData(0); - if (a) { - this.properties.disable_alpha ? gl.disable(gl.BLEND) : (gl.enable(gl.BLEND), this.properties.additive ? gl.blendFunc(gl.SRC_ALPHA, gl.ONE) : gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)); - gl.disable(gl.DEPTH_TEST); - var b = this.properties.gamma || 1.0; - this.isInputConnected(1) && (b = this.getInputData(1)); - a.setParameter(gl.TEXTURE_MAG_FILTER, this.properties.filter ? gl.LINEAR : gl.NEAREST); - if (this.properties.antialiasing) { - A._shader || (A._shader = new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER, A.aa_pixel_shader)); - gl.getViewport(); - var c = Mesh.getScreenQuad(); - a.bind(0); - A._shader.uniforms({u_texture:0, uViewportSize:[a.width, a.height], u_igamma:1 / b, inverseVP:[1 / a.width, 1 / a.height]}).draw(c); - } else { - 1.0 != b ? (A._gamma_shader || (A._gamma_shader = new GL.Shader(Shader.SCREEN_VERTEX_SHADER, A.gamma_pixel_shader)), a.toViewport(A._gamma_shader, {u_texture:0, u_igamma:1 / b})) : a.toViewport(); - } - } - }; - A.prototype.onGetInputs = function() { - return [["gamma", "number"]]; - }; - A.aa_pixel_shader = "precision highp float;\n\r\n\t\t\tprecision highp float;\n\r\n\t\t\tvarying vec2 v_coord;\n\r\n\t\t\tuniform sampler2D u_texture;\n\r\n\t\t\tuniform vec2 uViewportSize;\n\r\n\t\t\tuniform vec2 inverseVP;\n\r\n\t\t\tuniform float u_igamma;\n\r\n\t\t\t#define FXAA_REDUCE_MIN (1.0/ 128.0)\n\r\n\t\t\t#define FXAA_REDUCE_MUL (1.0 / 8.0)\n\r\n\t\t\t#define FXAA_SPAN_MAX 8.0\n\r\n\t\t\t\n\r\n\t\t\t/* from mitsuhiko/webgl-meincraft based on the code on geeks3d.com */\n\r\n\t\t\tvec4 applyFXAA(sampler2D tex, vec2 fragCoord)\n\r\n\t\t\t{\n\r\n\t\t\t\tvec4 color = vec4(0.0);\n\r\n\t\t\t\t/*vec2 inverseVP = vec2(1.0 / uViewportSize.x, 1.0 / uViewportSize.y);*/\n\r\n\t\t\t\tvec3 rgbNW = texture2D(tex, (fragCoord + vec2(-1.0, -1.0)) * inverseVP).xyz;\n\r\n\t\t\t\tvec3 rgbNE = texture2D(tex, (fragCoord + vec2(1.0, -1.0)) * inverseVP).xyz;\n\r\n\t\t\t\tvec3 rgbSW = texture2D(tex, (fragCoord + vec2(-1.0, 1.0)) * inverseVP).xyz;\n\r\n\t\t\t\tvec3 rgbSE = texture2D(tex, (fragCoord + vec2(1.0, 1.0)) * inverseVP).xyz;\n\r\n\t\t\t\tvec3 rgbM = texture2D(tex, fragCoord * inverseVP).xyz;\n\r\n\t\t\t\tvec3 luma = vec3(0.299, 0.587, 0.114);\n\r\n\t\t\t\tfloat lumaNW = dot(rgbNW, luma);\n\r\n\t\t\t\tfloat lumaNE = dot(rgbNE, luma);\n\r\n\t\t\t\tfloat lumaSW = dot(rgbSW, luma);\n\r\n\t\t\t\tfloat lumaSE = dot(rgbSE, luma);\n\r\n\t\t\t\tfloat lumaM = dot(rgbM, luma);\n\r\n\t\t\t\tfloat lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE)));\n\r\n\t\t\t\tfloat lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE)));\n\r\n\t\t\t\t\n\r\n\t\t\t\tvec2 dir;\n\r\n\t\t\t\tdir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE));\n\r\n\t\t\t\tdir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE));\n\r\n\t\t\t\t\n\r\n\t\t\t\tfloat dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) * (0.25 * FXAA_REDUCE_MUL), FXAA_REDUCE_MIN);\n\r\n\t\t\t\t\n\r\n\t\t\t\tfloat rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce);\n\r\n\t\t\t\tdir = min(vec2(FXAA_SPAN_MAX, FXAA_SPAN_MAX), max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX), dir * rcpDirMin)) * inverseVP;\n\r\n\t\t\t\t\n\r\n\t\t\t\tvec3 rgbA = 0.5 * (texture2D(tex, fragCoord * inverseVP + dir * (1.0 / 3.0 - 0.5)).xyz + \n\r\n\t\t\t\t\ttexture2D(tex, fragCoord * inverseVP + dir * (2.0 / 3.0 - 0.5)).xyz);\n\r\n\t\t\t\tvec3 rgbB = rgbA * 0.5 + 0.25 * (texture2D(tex, fragCoord * inverseVP + dir * -0.5).xyz + \n\r\n\t\t\t\t\ttexture2D(tex, fragCoord * inverseVP + dir * 0.5).xyz);\n\r\n\t\t\t\t\n\r\n\t\t\t\t//return vec4(rgbA,1.0);\n\r\n\t\t\t\tfloat lumaB = dot(rgbB, luma);\n\r\n\t\t\t\tif ((lumaB < lumaMin) || (lumaB > lumaMax))\n\r\n\t\t\t\t\tcolor = vec4(rgbA, 1.0);\n\r\n\t\t\t\telse\n\r\n\t\t\t\t\tcolor = vec4(rgbB, 1.0);\n\r\n\t\t\t\tif(u_igamma != 1.0)\n\r\n\t\t\t\t\tcolor.xyz = pow( color.xyz, vec3(u_igamma) );\n\r\n\t\t\t\treturn color;\n\r\n\t\t\t}\n\r\n\t\t\t\n\r\n\t\t\tvoid main() {\n\r\n\t\t\t gl_FragColor = applyFXAA( u_texture, v_coord * uViewportSize) ;\n\r\n\t\t\t}\n\r\n\t\t\t"; - A.gamma_pixel_shader = "precision highp float;\n\r\n\t\t\tprecision highp float;\n\r\n\t\t\tvarying vec2 v_coord;\n\r\n\t\t\tuniform sampler2D u_texture;\n\r\n\t\t\tuniform float u_igamma;\n\r\n\t\t\tvoid main() {\n\r\n\t\t\t\tvec4 color = texture2D( u_texture, v_coord);\n\r\n\t\t\t\tcolor.xyz = pow(color.xyz, vec3(u_igamma) );\n\r\n\t\t\t gl_FragColor = color;\n\r\n\t\t\t}\n\r\n\t\t\t"; - f.registerNodeType("texture/toviewport", A); - H.title = "Copy"; - H.desc = "Copy Texture"; - H.widgets_info = {size:{widget:"combo", values:[0, 32, 64, 128, 256, 512, 1024, 2048]}, precision:{widget:"combo", values:t.MODE_VALUES}}; - H.prototype.onExecute = function() { - var a = this.getInputData(0); - if ((a || this._temp_texture) && this.isOutputConnected(0)) { - if (a) { - var b = a.width, c = a.height; - 0 != this.properties.size && (c = b = this.properties.size); - var d = this._temp_texture, e = a.type; - this.properties.precision === t.LOW ? e = gl.UNSIGNED_BYTE : this.properties.precision === t.HIGH && (e = gl.HIGH_PRECISION_FORMAT); - d && d.width == b && d.height == c && d.type == e || (d = gl.LINEAR, this.properties.generate_mipmaps && isPowerOfTwo(b) && isPowerOfTwo(c) && (d = gl.LINEAR_MIPMAP_LINEAR), this._temp_texture = new GL.Texture(b, c, {type:e, format:gl.RGBA, minFilter:d, magFilter:gl.LINEAR})); - a.copyTo(this._temp_texture); - this.properties.generate_mipmaps && (this._temp_texture.bind(0), gl.generateMipmap(this._temp_texture.texture_type), this._temp_texture.unbind(0)); - } - this.setOutputData(0, this._temp_texture); - } - }; - f.registerNodeType("texture/copy", H); - E.title = "Downsample"; - E.desc = "Downsample Texture"; - E.widgets_info = {iterations:{type:"number", step:1, precision:0, min:1}, precision:{widget:"combo", values:t.MODE_VALUES}}; - E.prototype.onExecute = function() { - var a = this.getInputData(0); - if ((a || this._temp_texture) && this.isOutputConnected(0) && a && a.texture_type === GL.TEXTURE_2D) { - var b = E._shader; - b || (E._shader = b = new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER, E.pixel_shader)); - var c = a.width | 0, d = a.height | 0, e = a.type; - this.properties.precision === t.LOW ? e = gl.UNSIGNED_BYTE : this.properties.precision === t.HIGH && (e = gl.HIGH_PRECISION_FORMAT); - var g = this.properties.iterations || 1, f = a, h = []; - e = {type:e, format:a.format}; - var m = vec2.create(), k = {u_offset:m}; - this._texture && GL.Texture.releaseTemporary(this._texture); - for (var l = 0; l < g; ++l) { - m[0] = 1 / c; - m[1] = 1 / d; - c = c >> 1 || 0; - d = d >> 1 || 0; - a = GL.Texture.getTemporary(c, d, e); - h.push(a); - f.setParameter(GL.TEXTURE_MAG_FILTER, GL.NEAREST); - f.copyTo(a, b, k); - if (1 == c && 1 == d) { - break; - } - f = a; - } - this._texture = h.pop(); - for (l = 0; l < h.length; ++l) { - GL.Texture.releaseTemporary(h[l]); - } - this.properties.generate_mipmaps && (this._texture.bind(0), gl.generateMipmap(this._texture.texture_type), this._texture.unbind(0)); - this.setOutputData(0, this._texture); - } - }; - E.pixel_shader = "precision highp float;\n\r\n\t\t\tprecision highp float;\n\r\n\t\t\tuniform sampler2D u_texture;\n\r\n\t\t\tuniform vec2 u_offset;\n\r\n\t\t\tvarying vec2 v_coord;\n\r\n\t\t\t\n\r\n\t\t\tvoid main() {\n\r\n\t\t\t\tvec4 color = texture2D(u_texture, v_coord );\n\r\n\t\t\t\tcolor += texture2D(u_texture, v_coord + vec2( u_offset.x, 0.0 ) );\n\r\n\t\t\t\tcolor += texture2D(u_texture, v_coord + vec2( 0.0, u_offset.y ) );\n\r\n\t\t\t\tcolor += texture2D(u_texture, v_coord + vec2( u_offset.x, u_offset.y ) );\n\r\n\t\t\t gl_FragColor = color * 0.25;\n\r\n\t\t\t}\n\r\n\t\t\t"; - f.registerNodeType("texture/downsample", E); - D.title = "Average"; - D.desc = "Compute a partial average (32 random samples) of a texture and stores it as a 1x1 pixel texture"; - D.prototype.onExecute = function() { - var a = this.getInputData(0); - if (a && (this.isOutputConnected(0) || this.isOutputConnected(1) || this.isOutputConnected(2))) { - if (!D._shader) { - D._shader = new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER, D.pixel_shader); - for (var b = new Float32Array(32), c = 0; 32 > c; ++c) { - b[c] = Math.random(); - } - D._shader.uniforms({u_samples_a:b.subarray(0, 16), u_samples_b:b.subarray(16, 32)}); - } - c = this._temp_texture; - b = gl.UNSIGNED_BYTE; - a.type != b && (b = gl.FLOAT); - c && c.type == b || (this._temp_texture = new GL.Texture(1, 1, {type:b, format:gl.RGBA, filter:gl.NEAREST})); - var d = D._shader, e = this._uniforms; - e.u_mipmap_offset = this.properties.mipmap_offset; - this._temp_texture.drawTo(function() { - a.toViewport(d, e); - }); - this.setOutputData(0, this._temp_texture); - if (this.isOutputConnected(1) || this.isOutputConnected(2)) { - if (c = this._temp_texture.getPixels()) { - var g = this._luminance; - b = this._temp_texture.type; - g.set(c); - b == gl.UNSIGNED_BYTE ? vec4.scale(g, g, 1 / 255) : (b == GL.HALF_FLOAT || b == GL.HALF_FLOAT_OES) && vec4.scale(g, g, 1 / 65025); - this.setOutputData(1, g); - this.setOutputData(2, (g[0] + g[1] + g[2]) / 3); - } - } - } - }; - D.pixel_shader = "precision highp float;\n\r\n\t\t\tprecision highp float;\n\r\n\t\t\tuniform mat4 u_samples_a;\n\r\n\t\t\tuniform mat4 u_samples_b;\n\r\n\t\t\tuniform sampler2D u_texture;\n\r\n\t\t\tuniform float u_mipmap_offset;\n\r\n\t\t\tvarying vec2 v_coord;\n\r\n\t\t\t\n\r\n\t\t\tvoid main() {\n\r\n\t\t\t\tvec4 color = vec4(0.0);\n\r\n\t\t\t\tfor(int i = 0; i < 4; ++i)\n\r\n\t\t\t\t\tfor(int j = 0; j < 4; ++j)\n\r\n\t\t\t\t\t{\n\r\n\t\t\t\t\t\tcolor += texture2D(u_texture, vec2( u_samples_a[i][j], u_samples_b[i][j] ), u_mipmap_offset );\n\r\n\t\t\t\t\t\tcolor += texture2D(u_texture, vec2( 1.0 - u_samples_a[i][j], 1.0 - u_samples_b[i][j] ), u_mipmap_offset );\n\r\n\t\t\t\t\t}\n\r\n\t\t\t gl_FragColor = color * 0.03125;\n\r\n\t\t\t}\n\r\n\t\t\t"; - f.registerNodeType("texture/average", D); - G.title = "Image to Texture"; - G.desc = "Uploads an image to the GPU"; - G.prototype.onExecute = function() { - var a = this.getInputData(0); - if (a) { - var b = a.videoWidth || a.width, c = a.videoHeight || a.height; - if (a.gltexture) { - this.setOutputData(0, a.gltexture); - } else { - var d = this._temp_texture; - d && d.width == b && d.height == c || (this._temp_texture = new GL.Texture(b, c, {format:gl.RGBA, filter:gl.LINEAR})); - try { - this._temp_texture.uploadImage(a); - } catch (L) { - console.error("image comes from an unsafe location, cannot be uploaded to webgl: " + L); - return; - } - this.setOutputData(0, this._temp_texture); - } - } - }; - f.registerNodeType("texture/imageToTexture", G); - B.widgets_info = {texture:{widget:"texture"}, precision:{widget:"combo", values:t.MODE_VALUES}}; - B.title = "LUT"; - B.desc = "Apply LUT to Texture"; - B.prototype.onExecute = function() { - if (this.isOutputConnected(0)) { - var a = this.getInputData(0); - if (this.properties.precision === t.PASS_THROUGH) { - this.setOutputData(0, a); - } else { - if (a) { - var b = this.getInputData(1); - b || (b = t.getTexture(this.properties.texture)); - if (b) { - b.bind(0); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); - gl.bindTexture(gl.TEXTURE_2D, null); - var c = this.properties.intensity; - this.isInputConnected(2) && (this.properties.intensity = c = this.getInputData(2)); - this._tex = t.getTargetTexture(a, this._tex, this.properties.precision); - this._tex.drawTo(function() { - b.bind(1); - a.toViewport(B._shader, {u_texture:0, u_textureB:1, u_amount:c}); - }); - this.setOutputData(0, this._tex); - } else { - this.setOutputData(0, a); - } - } - } - } - }; - B.pixel_shader = "precision highp float;\n\r\n\t\t\tprecision highp float;\n\r\n\t\t\tvarying vec2 v_coord;\n\r\n\t\t\tuniform sampler2D u_texture;\n\r\n\t\t\tuniform sampler2D u_textureB;\n\r\n\t\t\tuniform float u_amount;\n\r\n\t\t\t\n\r\n\t\t\tvoid main() {\n\r\n\t\t\t\t lowp vec4 textureColor = clamp( texture2D(u_texture, v_coord), vec4(0.0), vec4(1.0) );\n\r\n\t\t\t\t mediump float blueColor = textureColor.b * 63.0;\n\r\n\t\t\t\t mediump vec2 quad1;\n\r\n\t\t\t\t quad1.y = floor(floor(blueColor) / 8.0);\n\r\n\t\t\t\t quad1.x = floor(blueColor) - (quad1.y * 8.0);\n\r\n\t\t\t\t mediump vec2 quad2;\n\r\n\t\t\t\t quad2.y = floor(ceil(blueColor) / 8.0);\n\r\n\t\t\t\t quad2.x = ceil(blueColor) - (quad2.y * 8.0);\n\r\n\t\t\t\t highp vec2 texPos1;\n\r\n\t\t\t\t texPos1.x = (quad1.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r);\n\r\n\t\t\t\t texPos1.y = 1.0 - ((quad1.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.g));\n\r\n\t\t\t\t highp vec2 texPos2;\n\r\n\t\t\t\t texPos2.x = (quad2.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r);\n\r\n\t\t\t\t texPos2.y = 1.0 - ((quad2.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.g));\n\r\n\t\t\t\t lowp vec4 newColor1 = texture2D(u_textureB, texPos1);\n\r\n\t\t\t\t lowp vec4 newColor2 = texture2D(u_textureB, texPos2);\n\r\n\t\t\t\t lowp vec4 newColor = mix(newColor1, newColor2, fract(blueColor));\n\r\n\t\t\t\t gl_FragColor = vec4( mix( textureColor.rgb, newColor.rgb, u_amount), textureColor.w);\n\r\n\t\t\t}\n\r\n\t\t\t"; - f.registerNodeType("texture/LUT", B); - p.title = "Texture to Channels"; - p.desc = "Split texture channels"; - p.prototype.onExecute = function() { - var a = this.getInputData(0); - if (a) { - this._channels || (this._channels = Array(4)); - for (var b = 0, c = 0; 4 > c; c++) { - this.isOutputConnected(c) ? (this._channels[c] && this._channels[c].width == a.width && this._channels[c].height == a.height && this._channels[c].type == a.type || (this._channels[c] = new GL.Texture(a.width, a.height, {type:a.type, format:gl.RGBA, filter:gl.LINEAR})), b++) : this._channels[c] = null; - } - if (b) { - gl.disable(gl.BLEND); - gl.disable(gl.DEPTH_TEST); - var d = Mesh.getScreenQuad(), e = p._shader, g = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]; - for (c = 0; 4 > c; c++) { - this._channels[c] && (this._channels[c].drawTo(function() { - a.bind(0); - e.uniforms({u_texture:0, u_mask:g[c]}).draw(d); - }), this.setOutputData(c, this._channels[c])); - } - } - } - }; - p.pixel_shader = "precision highp float;\n\r\n\t\t\tprecision highp float;\n\r\n\t\t\tvarying vec2 v_coord;\n\r\n\t\t\tuniform sampler2D u_texture;\n\r\n\t\t\tuniform vec4 u_mask;\n\r\n\t\t\t\n\r\n\t\t\tvoid main() {\n\r\n\t\t\t gl_FragColor = vec4( vec3( length( texture2D(u_texture, v_coord) * u_mask )), 1.0 );\n\r\n\t\t\t}\n\r\n\t\t\t"; - f.registerNodeType("texture/textureChannels", p); - m.title = "Channels to Texture"; - m.desc = "Split texture channels"; - m.prototype.onExecute = function() { - var a = [this.getInputData(0), this.getInputData(1), this.getInputData(2), this.getInputData(3)]; - if (a[0] && a[1] && a[2] && a[3]) { - gl.disable(gl.BLEND); - gl.disable(gl.DEPTH_TEST); - var b = Mesh.getScreenQuad(), c = m._shader; - this._tex = t.getTargetTexture(a[0], this._tex); - this._tex.drawTo(function() { - a[0].bind(0); - a[1].bind(1); - a[2].bind(2); - a[3].bind(3); - c.uniforms({u_textureR:0, u_textureG:1, u_textureB:2, u_textureA:3}).draw(b); - }); - this.setOutputData(0, this._tex); - } - }; - m.pixel_shader = "precision highp float;\n\r\n\t\t\tprecision highp float;\n\r\n\t\t\tvarying vec2 v_coord;\n\r\n\t\t\tuniform sampler2D u_textureR;\n\r\n\t\t\tuniform sampler2D u_textureG;\n\r\n\t\t\tuniform sampler2D u_textureB;\n\r\n\t\t\tuniform sampler2D u_textureA;\n\r\n\t\t\t\n\r\n\t\t\tvoid main() {\n\r\n\t\t\t gl_FragColor = vec4( \r\n\t\t\t\t\t\ttexture2D(u_textureR, v_coord).r,\r\n\t\t\t\t\t\ttexture2D(u_textureG, v_coord).r,\r\n\t\t\t\t\t\ttexture2D(u_textureB, v_coord).r,\r\n\t\t\t\t\t\ttexture2D(u_textureA, v_coord).r);\n\r\n\t\t\t}\n\r\n\t\t\t"; - f.registerNodeType("texture/channelsTexture", m); - h.title = "Gradient"; - h.desc = "Generates a gradient"; - h["@A"] = {type:"color"}; - h["@B"] = {type:"color"}; - h["@texture_size"] = {type:"enum", values:[32, 64, 128, 256, 512]}; - h.prototype.onExecute = function() { - gl.disable(gl.BLEND); - gl.disable(gl.DEPTH_TEST); - var a = GL.Mesh.getScreenQuad(), b = h._shader, c = this.getInputData(0); - c || (c = this.properties.A); - var d = this.getInputData(1); - d || (d = this.properties.B); - for (var e = 2; e < this.inputs.length; e++) { - var g = this.inputs[e], f = this.getInputData(e); - void 0 !== f && (this.properties[g.name] = f); - } - var m = this._uniforms; - this._uniforms.u_angle = this.properties.angle * DEG2RAD; - this._uniforms.u_scale = this.properties.scale; - vec3.copy(m.u_colorA, c); - vec3.copy(m.u_colorB, d); - c = parseInt(this.properties.texture_size); - this._tex && this._tex.width == c || (this._tex = new GL.Texture(c, c, {format:gl.RGB, filter:gl.LINEAR})); - this._tex.drawTo(function() { - b.uniforms(m).draw(a); - }); - this.setOutputData(0, this._tex); - }; - h.prototype.onGetInputs = function() { - return [["angle", "number"], ["scale", "number"]]; - }; - h.pixel_shader = "precision highp float;\n\r\n\t\t\tprecision highp float;\n\r\n\t\t\tvarying vec2 v_coord;\n\r\n\t\t\tuniform float u_angle;\n\r\n\t\t\tuniform float u_scale;\n\r\n\t\t\tuniform vec3 u_colorA;\n\r\n\t\t\tuniform vec3 u_colorB;\n\r\n\t\t\t\n\r\n\t\t\tvec2 rotate(vec2 v, float angle)\n\r\n\t\t\t{\n\r\n\t\t\t\tvec2 result;\n\r\n\t\t\t\tfloat _cos = cos(angle);\n\r\n\t\t\t\tfloat _sin = sin(angle);\n\r\n\t\t\t\tresult.x = v.x * _cos - v.y * _sin;\n\r\n\t\t\t\tresult.y = v.x * _sin + v.y * _cos;\n\r\n\t\t\t\treturn result;\n\r\n\t\t\t}\n\r\n\t\t\tvoid main() {\n\r\n\t\t\t\tfloat f = (rotate(u_scale * (v_coord - vec2(0.5)), u_angle) + vec2(0.5)).x;\n\r\n\t\t\t\tvec3 color = mix(u_colorA,u_colorB,clamp(f,0.0,1.0));\n\r\n\t\t\t gl_FragColor = vec4(color,1.0);\n\r\n\t\t\t}\n\r\n\t\t\t"; - f.registerNodeType("texture/gradient", h); - y.title = "Mix"; - y.desc = "Generates a texture mixing two textures"; - y.widgets_info = {precision:{widget:"combo", values:t.MODE_VALUES}}; - y.prototype.onExecute = function() { - var a = this.getInputData(0); - if (this.isOutputConnected(0)) { - if (this.properties.precision === t.PASS_THROUGH) { - this.setOutputData(0, a); - } else { - var b = this.getInputData(1), c = this.getInputData(2); - if (a && b && c) { - this._tex = t.getTargetTexture(a, this._tex, this.properties.precision); - gl.disable(gl.BLEND); - gl.disable(gl.DEPTH_TEST); - var d = Mesh.getScreenQuad(), e = y._shader; - this._tex.drawTo(function() { - a.bind(0); - b.bind(1); - c.bind(2); - e.uniforms({u_textureA:0, u_textureB:1, u_textureMix:2}).draw(d); - }); - this.setOutputData(0, this._tex); - } - } - } - }; - y.pixel_shader = "precision highp float;\n\r\n\t\t\tprecision highp float;\n\r\n\t\t\tvarying vec2 v_coord;\n\r\n\t\t\tuniform sampler2D u_textureA;\n\r\n\t\t\tuniform sampler2D u_textureB;\n\r\n\t\t\tuniform sampler2D u_textureMix;\n\r\n\t\t\t\n\r\n\t\t\tvoid main() {\n\r\n\t\t\t gl_FragColor = mix( texture2D(u_textureA, v_coord), texture2D(u_textureB, v_coord), texture2D(u_textureMix, v_coord) );\n\r\n\t\t\t}\n\r\n\t\t\t"; - f.registerNodeType("texture/mix", y); - e.title = "Edges"; - e.desc = "Detects edges"; - e.widgets_info = {precision:{widget:"combo", values:t.MODE_VALUES}}; - e.prototype.onExecute = function() { - if (this.isOutputConnected(0)) { - var a = this.getInputData(0); - if (this.properties.precision === t.PASS_THROUGH) { - this.setOutputData(0, a); - } else { - if (a) { - this._tex = t.getTargetTexture(a, this._tex, this.properties.precision); - gl.disable(gl.BLEND); - gl.disable(gl.DEPTH_TEST); - var b = Mesh.getScreenQuad(), c = e._shader, d = this.properties.invert, g = this.properties.factor, f = this.properties.threshold ? 1 : 0; - this._tex.drawTo(function() { - a.bind(0); - c.uniforms({u_texture:0, u_isize:[1 / a.width, 1 / a.height], u_factor:g, u_threshold:f, u_invert:d ? 1 : 0}).draw(b); - }); - this.setOutputData(0, this._tex); - } - } - } - }; - e.pixel_shader = "precision highp float;\n\r\n\t\t\tprecision highp float;\n\r\n\t\t\tvarying vec2 v_coord;\n\r\n\t\t\tuniform sampler2D u_texture;\n\r\n\t\t\tuniform vec2 u_isize;\n\r\n\t\t\tuniform int u_invert;\n\r\n\t\t\tuniform float u_factor;\n\r\n\t\t\tuniform float u_threshold;\n\r\n\t\t\t\n\r\n\t\t\tvoid main() {\n\r\n\t\t\t\tvec4 center = texture2D(u_texture, v_coord);\n\r\n\t\t\t\tvec4 up = texture2D(u_texture, v_coord + u_isize * vec2(0.0,1.0) );\n\r\n\t\t\t\tvec4 down = texture2D(u_texture, v_coord + u_isize * vec2(0.0,-1.0) );\n\r\n\t\t\t\tvec4 left = texture2D(u_texture, v_coord + u_isize * vec2(1.0,0.0) );\n\r\n\t\t\t\tvec4 right = texture2D(u_texture, v_coord + u_isize * vec2(-1.0,0.0) );\n\r\n\t\t\t\tvec4 diff = abs(center - up) + abs(center - down) + abs(center - left) + abs(center - right);\n\r\n\t\t\t\tdiff *= u_factor;\n\r\n\t\t\t\tif(u_invert == 1)\n\r\n\t\t\t\t\tdiff.xyz = vec3(1.0) - diff.xyz;\n\r\n\t\t\t\tif( u_threshold == 0.0 )\n\r\n\t\t\t\t\tgl_FragColor = vec4( diff.xyz, center.a );\n\r\n\t\t\t\telse\n\r\n\t\t\t\t\tgl_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\r\n\t\t\t}\n\r\n\t\t\t"; - f.registerNodeType("texture/edges", e); - b.title = "Depth Range"; - b.desc = "Generates a texture with a depth range"; - b.prototype.onExecute = function() { - if (this.isOutputConnected(0)) { - var a = this.getInputData(0); - if (a) { - var c = gl.UNSIGNED_BYTE; - this.properties.high_precision && (c = gl.half_float_ext ? gl.HALF_FLOAT_OES : gl.FLOAT); - this._temp_texture && this._temp_texture.type == c && this._temp_texture.width == a.width && this._temp_texture.height == a.height || (this._temp_texture = new GL.Texture(a.width, a.height, {type:c, format:gl.RGBA, filter:gl.LINEAR})); - var d = this._uniforms; - c = this.properties.distance; - this.isInputConnected(1) && (c = this.getInputData(1), this.properties.distance = c); - var e = this.properties.range; - this.isInputConnected(2) && (e = this.getInputData(2), this.properties.range = e); - d.u_distance = c; - d.u_range = e; - gl.disable(gl.BLEND); - gl.disable(gl.DEPTH_TEST); - var g = Mesh.getScreenQuad(); - b._shader || (b._shader = new GL.Shader(Shader.SCREEN_VERTEX_SHADER, b.pixel_shader), b._shader_onlydepth = new GL.Shader(Shader.SCREEN_VERTEX_SHADER, b.pixel_shader, {ONLY_DEPTH:""})); - var f = this.properties.only_depth ? b._shader_onlydepth : b._shader; - c = null; - c = a.near_far_planes ? a.near_far_planes : window.LS && LS.Renderer._main_camera ? LS.Renderer._main_camera._uniforms.u_camera_planes : [0.1, 1000]; - d.u_camera_planes = c; - this._temp_texture.drawTo(function() { - a.bind(0); - f.uniforms(d).draw(g); - }); - this._temp_texture.near_far_planes = c; - this.setOutputData(0, this._temp_texture); - } - } - }; - b.pixel_shader = "precision highp float;\n\r\n\t\t\tprecision highp float;\n\r\n\t\t\tvarying vec2 v_coord;\n\r\n\t\t\tuniform sampler2D u_texture;\n\r\n\t\t\tuniform vec2 u_camera_planes;\n\r\n\t\t\tuniform float u_distance;\n\r\n\t\t\tuniform float u_range;\n\r\n\t\t\t\n\r\n\t\t\tfloat LinearDepth()\n\r\n\t\t\t{\n\r\n\t\t\t\tfloat zNear = u_camera_planes.x;\n\r\n\t\t\t\tfloat zFar = u_camera_planes.y;\n\r\n\t\t\t\tfloat depth = texture2D(u_texture, v_coord).x;\n\r\n\t\t\t\tdepth = depth * 2.0 - 1.0;\n\r\n\t\t\t\treturn zNear * (depth + 1.0) / (zFar + zNear - depth * (zFar - zNear));\n\r\n\t\t\t}\n\r\n\t\t\t\n\r\n\t\t\tvoid main() {\n\r\n\t\t\t\tfloat depth = LinearDepth();\n\r\n\t\t\t\t#ifdef ONLY_DEPTH\n\r\n\t\t\t\t gl_FragColor = vec4(depth);\n\r\n\t\t\t\t#else\n\r\n\t\t\t\t\tfloat diff = abs(depth * u_camera_planes.y - u_distance);\n\r\n\t\t\t\t\tfloat dof = 1.0;\n\r\n\t\t\t\t\tif(diff <= u_range)\n\r\n\t\t\t\t\t\tdof = diff / u_range;\n\r\n\t\t\t\t gl_FragColor = vec4(dof);\n\r\n\t\t\t\t#endif\n\r\n\t\t\t}\n\r\n\t\t\t"; - f.registerNodeType("texture/depth_range", b); - a.title = "Blur"; - a.desc = "Blur a texture"; - a.widgets_info = {precision:{widget:"combo", values:t.MODE_VALUES}}; - a.max_iterations = 20; - a.prototype.onExecute = function() { - var b = this.getInputData(0); - if (b && this.isOutputConnected(0)) { - var c = this._final_texture; - c && c.width == b.width && c.height == b.height && c.type == b.type || (c = this._final_texture = new GL.Texture(b.width, b.height, {type:b.type, format:gl.RGBA, filter:gl.LINEAR})); - var d = this.properties.iterations; - this.isInputConnected(1) && (d = this.getInputData(1), this.properties.iterations = d); - d = Math.min(Math.floor(d), a.max_iterations); - if (0 == d) { - this.setOutputData(0, b); - } else { - var e = this.properties.intensity; - this.isInputConnected(2) && (e = this.getInputData(2), this.properties.intensity = e); - var g = f.camera_aspect; - g || void 0 === window.gl || (g = gl.canvas.height / gl.canvas.width); - g || (g = 1); - g = this.properties.preserve_aspect ? g : 1; - var h = this.properties.scale || [1, 1]; - b.applyBlur(g * h[0], h[1], e, c); - for (b = 1; b < d; ++b) { - c.applyBlur(g * h[0] * (b + 1), h[1] * (b + 1), e); - } - this.setOutputData(0, c); - } - } - }; - f.registerNodeType("texture/blur", a); - c.title = "Glow"; - c.desc = "Filters a texture giving it a glow effect"; - c.weights = new Float32Array([0.5, 0.4, 0.3, 0.2]); - c.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:t.MODE_VALUES}}; - c.prototype.onGetInputs = function() { - return [["enabled", "boolean"], ["threshold", "number"], ["intensity", "number"], ["persistence", "number"], ["iterations", "number"], ["dirt_factor", "number"]]; - }; - c.prototype.onGetOutputs = function() { - return [["average", "Texture"]]; - }; - c.prototype.onExecute = function() { - var a = this.getInputData(0); - if (a && this.isAnyOutputConnected()) { - if (this.properties.precision === t.PASS_THROUGH || !1 === this.getInputOrProperty("enabled")) { - this.setOutputData(0, a); - } else { - var b = a.width, d = a.height, e = {format:a.format, type:a.type, minFilter:GL.LINEAR, magFilter:GL.LINEAR, wrap:gl.CLAMP_TO_EDGE}, g = t.getTextureType(this.properties.precision, a), f = this._uniforms, h = this._textures, m = c._cut_shader; - m || (m = c._cut_shader = new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER, c.cut_pixel_shader)); - gl.disable(gl.DEPTH_TEST); - gl.disable(gl.BLEND); - f.u_threshold = this.getInputOrProperty("threshold"); - var k = h[0] = GL.Texture.getTemporary(b, d, e); - a.blit(k, m.uniforms(f)); - var l = k, p = this.getInputOrProperty("iterations"); - p = Math.clamp(p, 1, 16) | 0; - var n = f.u_texel_size, q = this.getInputOrProperty("intensity"); - f.u_intensity = 1; - f.u_delta = this.properties.scale; - m = c._shader; - m || (m = c._shader = new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER, c.scale_pixel_shader)); - for (var r = 1; r < p; r++) { - b >>= 1; - 1 < (d | 0) && (d >>= 1); - if (2 > b) { - break; - } - k = h[r] = GL.Texture.getTemporary(b, d, e); - n[0] = 1 / l.width; - n[1] = 1 / l.height; - l.blit(k, m.uniforms(f)); - l = k; - } - this.isOutputConnected(2) && (b = this._average_texture, b && b.type == a.type && b.format == a.format || (b = this._average_texture = new GL.Texture(1, 1, {type:a.type, format:a.format, filter:gl.LINEAR})), n[0] = 1 / l.width, n[1] = 1 / l.height, f.u_intensity = q, f.u_delta = 1, l.blit(b, m.uniforms(f)), this.setOutputData(2, b)); - gl.enable(gl.BLEND); - gl.blendFunc(gl.ONE, gl.ONE); - f.u_intensity = this.getInputOrProperty("persistence"); - f.u_delta = 0.5; - for (r -= 2; 0 <= r; r--) { - k = h[r], h[r] = null, n[0] = 1 / l.width, n[1] = 1 / l.height, l.blit(k, m.uniforms(f)), GL.Texture.releaseTemporary(l), l = k; - } - gl.disable(gl.BLEND); - this.isOutputConnected(1) && (h = this._glow_texture, h && h.width == a.width && h.height == a.height && h.type == g && h.format == a.format || (h = this._glow_texture = new GL.Texture(a.width, a.height, {type:g, format:a.format, filter:gl.LINEAR})), l.blit(h), this.setOutputData(1, h)); - if (this.isOutputConnected(0)) { - h = this._final_texture; - h && h.width == a.width && h.height == a.height && h.type == g && h.format == a.format || (h = this._final_texture = new GL.Texture(a.width, a.height, {type:g, format:a.format, filter:gl.LINEAR})); - var v = this.getInputData(1), u = this.getInputOrProperty("dirt_factor"); - f.u_intensity = q; - m = v ? c._dirt_final_shader : c._final_shader; - m || (m = v ? c._dirt_final_shader = new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER, c.final_pixel_shader, {USE_DIRT:""}) : c._final_shader = new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER, c.final_pixel_shader)); - h.drawTo(function() { - a.bind(0); - l.bind(1); - v && (m.setUniform("u_dirt_factor", u), m.setUniform("u_dirt_texture", v.bind(2))); - m.toViewport(f); - }); - this.setOutputData(0, h); - } - GL.Texture.releaseTemporary(l); - } - } - }; - c.cut_pixel_shader = "precision highp float;\n\r\n\t\tvarying vec2 v_coord;\n\r\n\t\tuniform sampler2D u_texture;\n\r\n\t\tuniform float u_threshold;\n\r\n\t\tvoid main() {\n\r\n\t\t\tgl_FragColor = max( texture2D( u_texture, v_coord ) - vec4( u_threshold ), vec4(0.0) );\n\r\n\t\t}"; - c.scale_pixel_shader = "precision highp float;\n\r\n\t\tvarying vec2 v_coord;\n\r\n\t\tuniform sampler2D u_texture;\n\r\n\t\tuniform vec2 u_texel_size;\n\r\n\t\tuniform float u_delta;\n\r\n\t\tuniform float u_intensity;\n\r\n\t\t\n\r\n\t\tvec4 sampleBox(vec2 uv) {\n\r\n\t\t\tvec4 o = u_texel_size.xyxy * vec2(-u_delta, u_delta).xxyy;\n\r\n\t\t\tvec4 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\r\n\t\t\treturn s * 0.25;\n\r\n\t\t}\n\r\n\t\tvoid main() {\n\r\n\t\t\tgl_FragColor = u_intensity * sampleBox( v_coord );\n\r\n\t\t}"; - c.final_pixel_shader = "precision highp float;\n\r\n\t\tvarying vec2 v_coord;\n\r\n\t\tuniform sampler2D u_texture;\n\r\n\t\tuniform sampler2D u_glow_texture;\n\r\n\t\t#ifdef USE_DIRT\n\r\n\t\t\tuniform sampler2D u_dirt_texture;\n\r\n\t\t#endif\n\r\n\t\tuniform vec2 u_texel_size;\n\r\n\t\tuniform float u_delta;\n\r\n\t\tuniform float u_intensity;\n\r\n\t\tuniform float u_dirt_factor;\n\r\n\t\t\n\r\n\t\tvec4 sampleBox(vec2 uv) {\n\r\n\t\t\tvec4 o = u_texel_size.xyxy * vec2(-u_delta, u_delta).xxyy;\n\r\n\t\t\tvec4 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\r\n\t\t\treturn s * 0.25;\n\r\n\t\t}\n\r\n\t\tvoid main() {\n\r\n\t\t\tvec4 glow = sampleBox( v_coord );\n\r\n\t\t\t#ifdef USE_DIRT\n\r\n\t\t\t\tglow = mix( glow, glow * texture2D( u_dirt_texture, v_coord ), u_dirt_factor );\n\r\n\t\t\t#endif\n\r\n\t\t\tgl_FragColor = texture2D( u_texture, v_coord ) + u_intensity * glow;\n\r\n\t\t}"; - f.registerNodeType("texture/glow", c); - d.title = "Kuwahara Filter"; - d.desc = "Filters a texture giving an artistic oil canvas painting"; - d.max_radius = 10; - d._shaders = []; - d.prototype.onExecute = function() { - var a = this.getInputData(0); - if (a && this.isOutputConnected(0)) { - var b = this._temp_texture; - b && b.width == a.width && b.height == a.height && b.type == a.type || (this._temp_texture = new GL.Texture(a.width, a.height, {type:a.type, format:gl.RGBA, filter:gl.LINEAR})); - b = this.properties.radius; - b = Math.min(Math.floor(b), d.max_radius); - if (0 == b) { - this.setOutputData(0, a); - } else { - var c = this.properties.intensity, e = f.camera_aspect; - e || void 0 === window.gl || (e = gl.canvas.height / gl.canvas.width); - e || (e = 1); - e = this.properties.preserve_aspect ? e : 1; - d._shaders[b] || (d._shaders[b] = new GL.Shader(Shader.SCREEN_VERTEX_SHADER, d.pixel_shader, {RADIUS:b.toFixed(0)})); - var g = d._shaders[b], h = GL.Mesh.getScreenQuad(); - a.bind(0); - this._temp_texture.drawTo(function() { - g.uniforms({u_texture:0, u_intensity:c, u_resolution:[a.width, a.height], u_iResolution:[1 / a.width, 1 / a.height]}).draw(h); - }); - this.setOutputData(0, this._temp_texture); - } - } - }; - d.pixel_shader = "\n\r\n\tprecision highp float;\n\r\n\tvarying vec2 v_coord;\n\r\n\tuniform sampler2D u_texture;\n\r\n\tuniform float u_intensity;\n\r\n\tuniform vec2 u_resolution;\n\r\n\tuniform vec2 u_iResolution;\n\r\n\t#ifndef RADIUS\n\r\n\t\t#define RADIUS 7\n\r\n\t#endif\n\r\n\tvoid main() {\n\r\n\t\n\r\n\t\tconst int radius = RADIUS;\n\r\n\t\tvec2 fragCoord = v_coord;\n\r\n\t\tvec2 src_size = u_iResolution;\n\r\n\t\tvec2 uv = v_coord;\n\r\n\t\tfloat n = float((radius + 1) * (radius + 1));\n\r\n\t\tint i;\n\r\n\t\tint j;\n\r\n\t\tvec3 m0 = vec3(0.0); vec3 m1 = vec3(0.0); vec3 m2 = vec3(0.0); vec3 m3 = vec3(0.0);\n\r\n\t\tvec3 s0 = vec3(0.0); vec3 s1 = vec3(0.0); vec3 s2 = vec3(0.0); vec3 s3 = vec3(0.0);\n\r\n\t\tvec3 c;\n\r\n\t\t\n\r\n\t\tfor (int j = -radius; j <= 0; ++j) {\n\r\n\t\t\tfor (int i = -radius; i <= 0; ++i) {\n\r\n\t\t\t\tc = texture2D(u_texture, uv + vec2(i,j) * src_size).rgb;\n\r\n\t\t\t\tm0 += c;\n\r\n\t\t\t\ts0 += c * c;\n\r\n\t\t\t}\n\r\n\t\t}\n\r\n\t\t\n\r\n\t\tfor (int j = -radius; j <= 0; ++j) {\n\r\n\t\t\tfor (int i = 0; i <= radius; ++i) {\n\r\n\t\t\t\tc = texture2D(u_texture, uv + vec2(i,j) * src_size).rgb;\n\r\n\t\t\t\tm1 += c;\n\r\n\t\t\t\ts1 += c * c;\n\r\n\t\t\t}\n\r\n\t\t}\n\r\n\t\t\n\r\n\t\tfor (int j = 0; j <= radius; ++j) {\n\r\n\t\t\tfor (int i = 0; i <= radius; ++i) {\n\r\n\t\t\t\tc = texture2D(u_texture, uv + vec2(i,j) * src_size).rgb;\n\r\n\t\t\t\tm2 += c;\n\r\n\t\t\t\ts2 += c * c;\n\r\n\t\t\t}\n\r\n\t\t}\n\r\n\t\t\n\r\n\t\tfor (int j = 0; j <= radius; ++j) {\n\r\n\t\t\tfor (int i = -radius; i <= 0; ++i) {\n\r\n\t\t\t\tc = texture2D(u_texture, uv + vec2(i,j) * src_size).rgb;\n\r\n\t\t\t\tm3 += c;\n\r\n\t\t\t\ts3 += c * c;\n\r\n\t\t\t}\n\r\n\t\t}\n\r\n\t\t\n\r\n\t\tfloat min_sigma2 = 1e+2;\n\r\n\t\tm0 /= n;\n\r\n\t\ts0 = abs(s0 / n - m0 * m0);\n\r\n\t\t\n\r\n\t\tfloat sigma2 = s0.r + s0.g + s0.b;\n\r\n\t\tif (sigma2 < min_sigma2) {\n\r\n\t\t\tmin_sigma2 = sigma2;\n\r\n\t\t\tgl_FragColor = vec4(m0, 1.0);\n\r\n\t\t}\n\r\n\t\t\n\r\n\t\tm1 /= n;\n\r\n\t\ts1 = abs(s1 / n - m1 * m1);\n\r\n\t\t\n\r\n\t\tsigma2 = s1.r + s1.g + s1.b;\n\r\n\t\tif (sigma2 < min_sigma2) {\n\r\n\t\t\tmin_sigma2 = sigma2;\n\r\n\t\t\tgl_FragColor = vec4(m1, 1.0);\n\r\n\t\t}\n\r\n\t\t\n\r\n\t\tm2 /= n;\n\r\n\t\ts2 = abs(s2 / n - m2 * m2);\n\r\n\t\t\n\r\n\t\tsigma2 = s2.r + s2.g + s2.b;\n\r\n\t\tif (sigma2 < min_sigma2) {\n\r\n\t\t\tmin_sigma2 = sigma2;\n\r\n\t\t\tgl_FragColor = vec4(m2, 1.0);\n\r\n\t\t}\n\r\n\t\t\n\r\n\t\tm3 /= n;\n\r\n\t\ts3 = abs(s3 / n - m3 * m3);\n\r\n\t\t\n\r\n\t\tsigma2 = s3.r + s3.g + s3.b;\n\r\n\t\tif (sigma2 < min_sigma2) {\n\r\n\t\t\tmin_sigma2 = sigma2;\n\r\n\t\t\tgl_FragColor = vec4(m3, 1.0);\n\r\n\t\t}\n\r\n\t}\n\r\n\t"; - f.registerNodeType("texture/kuwahara", d); - l.title = "Webcam"; - l.desc = "Webcam texture"; - l.prototype.openStream = function() { - navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia; - window.URL = window.URL || window.webkitURL; - if (navigator.getUserMedia) { - this._waiting_confirmation = !0; - var a = this; - navigator.getUserMedia({video:!0}, this.streamReady.bind(this), function(b) { - console.log("Webcam rejected", b); - a._webcam_stream = !1; - a.box_color = "red"; - }); - } - }; - l.prototype.streamReady = function(a) { - this._webcam_stream = a; - var b = this._video; - b || (b = document.createElement("video"), b.autoplay = !0, b.src = window.URL.createObjectURL(a), this._video = b, b.onloadedmetadata = function(a) { - console.log(a); - }); - }; - l.prototype.onRemoved = function() { - if (this._webcam_stream) { - var a = this._webcam_stream.getVideoTracks(); - a.length && (a = a[0], a.stop && a.stop()); - this._video = this._webcam_stream = null; - } - }; - l.prototype.onDrawBackground = function(a) { - this.flags.collapsed || 20 >= this.size[1] || !this._video || (a.save(), a.webgl ? this._temp_texture && a.drawImage(this._temp_texture, 0, 0, this.size[0], this.size[1]) : (a.translate(0, this.size[1]), a.scale(1, -1), a.drawImage(this._video, 0, 0, this.size[0], this.size[1])), a.restore()); - }; - l.prototype.onExecute = function() { - null != this._webcam_stream || this._waiting_confirmation || this.openStream(); - if (this._video && this._video.videoWidth) { - var a = this._video.videoWidth, b = this._video.videoHeight, c = this._temp_texture; - c && c.width == a && c.height == b || (this._temp_texture = new GL.Texture(a, b, {format:gl.RGB, filter:gl.LINEAR})); - this._temp_texture.uploadImage(this._video); - this.properties.texture_name && (t.getTexturesContainer()[this.properties.texture_name] = this._temp_texture); - this.setOutputData(0, this._temp_texture); - } - }; - f.registerNodeType("texture/webcam", l); - w.title = "Lens FX"; - w.desc = "distortion and chromatic aberration"; - w.widgets_info = {precision:{widget:"combo", values:t.MODE_VALUES}}; - w.prototype.onGetInputs = function() { - return [["enabled", "boolean"]]; - }; - w.prototype.onExecute = function() { - var a = this.getInputData(0); - if (a && this.isOutputConnected(0)) { - if (this.properties.precision === t.PASS_THROUGH || !1 === this.getInputOrProperty("enabled")) { - this.setOutputData(0, a); - } else { - var b = this._temp_texture; - b && b.width == a.width && b.height == a.height && b.type == a.type || (b = this._temp_texture = new GL.Texture(a.width, a.height, {type:a.type, format:gl.RGBA, filter:gl.LINEAR})); - var c = w._shader; - c || (c = w._shader = new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER, w.pixel_shader)); - var d = this.getInputData(1); - null == d && (d = this.properties.factor); - var e = this._uniforms; - e.u_factor = d; - gl.disable(gl.DEPTH_TEST); - b.drawTo(function() { - a.bind(0); - c.uniforms(e).draw(GL.Mesh.getScreenQuad()); - }); - this.setOutputData(0, b); - } - } - }; - w.pixel_shader = "precision highp float;\n\r\n\t\t\tvarying vec2 v_coord;\n\r\n\t\t\tuniform sampler2D u_texture;\n\r\n\t\t\tuniform float u_factor;\n\r\n\t\t\tvec2 barrelDistortion(vec2 coord, float amt) {\n\r\n\t\t\t\tvec2 cc = coord - 0.5;\n\r\n\t\t\t\tfloat dist = dot(cc, cc);\n\r\n\t\t\t\treturn coord + cc * dist * amt;\n\r\n\t\t\t}\n\r\n\t\t\t\n\r\n\t\t\tfloat sat( float t )\n\r\n\t\t\t{\n\r\n\t\t\t\treturn clamp( t, 0.0, 1.0 );\n\r\n\t\t\t}\n\r\n\t\t\t\n\r\n\t\t\tfloat linterp( float t ) {\n\r\n\t\t\t\treturn sat( 1.0 - abs( 2.0*t - 1.0 ) );\n\r\n\t\t\t}\n\r\n\t\t\t\n\r\n\t\t\tfloat remap( float t, float a, float b ) {\n\r\n\t\t\t\treturn sat( (t - a) / (b - a) );\n\r\n\t\t\t}\n\r\n\t\t\t\n\r\n\t\t\tvec4 spectrum_offset( float t ) {\n\r\n\t\t\t\tvec4 ret;\n\r\n\t\t\t\tfloat lo = step(t,0.5);\n\r\n\t\t\t\tfloat hi = 1.0-lo;\n\r\n\t\t\t\tfloat w = linterp( remap( t, 1.0/6.0, 5.0/6.0 ) );\n\r\n\t\t\t\tret = vec4(lo,1.0,hi, 1.) * vec4(1.0-w, w, 1.0-w, 1.);\n\r\n\t\t\t\n\r\n\t\t\t\treturn pow( ret, vec4(1.0/2.2) );\n\r\n\t\t\t}\n\r\n\t\t\t\n\r\n\t\t\tconst float max_distort = 2.2;\n\r\n\t\t\tconst int num_iter = 12;\n\r\n\t\t\tconst float reci_num_iter_f = 1.0 / float(num_iter);\n\r\n\t\t\t\n\r\n\t\t\tvoid main()\n\r\n\t\t\t{\t\n\r\n\t\t\t\tvec2 uv=v_coord;\n\r\n\t\t\t\tvec4 sumcol = vec4(0.0);\n\r\n\t\t\t\tvec4 sumw = vec4(0.0);\t\n\r\n\t\t\t\tfor ( int i=0; i= this.size[1] || !a.webgl || gl.meshes.cube || (gl.meshes.cube = GL.Mesh.cube({size:1})); - }; - f.registerNodeType("texture/cubemap", k); - } -})(this); -(function(u) { - var f = u.LiteGraph; - if ("undefined" != typeof GL) { - var k = function() { - this.addInput("Tex.", "Texture"); - this.addInput("intensity", "number"); - this.addOutput("Texture", "Texture"); - this.properties = {intensity:1, invert:!1, precision:LGraphTexture.DEFAULT}; - k._shader || (k._shader = new GL.Shader(Shader.SCREEN_VERTEX_SHADER, k.pixel_shader)); - }, n = function() { - this.addInput("Texture", "Texture"); - this.addInput("value1", "number"); - this.addInput("value2", "number"); - this.addOutput("Texture", "Texture"); - this.properties = {fx:"halftone", value1:1, value2:1, precision:LGraphTexture.DEFAULT}; - }, g = function() { - this.addInput("Texture", "Texture"); - this.addInput("Blurred", "Texture"); - this.addInput("Mask", "Texture"); - this.addInput("Threshold", "number"); - this.addOutput("Texture", "Texture"); - this.properties = {shape:"", size:10, alpha:1.0, threshold:1.0, high_precision:!1}; - }, q = function() { - this.addInput("Texture", "Texture"); - this.addInput("Aberration", "number"); - this.addInput("Distortion", "number"); - this.addInput("Blur", "number"); - this.addOutput("Texture", "Texture"); - this.properties = {aberration:1.0, distortion:1.0, blur:1.0, precision:LGraphTexture.DEFAULT}; - q._shader || (q._shader = new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER, q.pixel_shader), q._texture = new GL.Texture(3, 1, {format:gl.RGB, wrap:gl.CLAMP_TO_EDGE, magFilter:gl.LINEAR, minFilter:gl.LINEAR, pixel_data:[255, 0, 0, 0, 255, 0, 0, 0, 255]})); - }; - q.title = "Lens"; - q.desc = "Camera Lens distortion"; - q.widgets_info = {precision:{widget:"combo", values:LGraphTexture.MODE_VALUES}}; - q.prototype.onExecute = function() { - var g = this.getInputData(0); - if (this.properties.precision === LGraphTexture.PASS_THROUGH) { - this.setOutputData(0, g); - } else { - if (g) { - this._tex = LGraphTexture.getTargetTexture(g, this._tex, this.properties.precision); - var f = this.properties.aberration; - this.isInputConnected(1) && (f = this.getInputData(1), this.properties.aberration = f); - var k = this.properties.distortion; - this.isInputConnected(2) && (k = this.getInputData(2), this.properties.distortion = k); - var l = this.properties.blur; - this.isInputConnected(3) && (l = this.getInputData(3), this.properties.blur = l); - gl.disable(gl.BLEND); - gl.disable(gl.DEPTH_TEST); - var d = Mesh.getScreenQuad(), c = q._shader; - this._tex.drawTo(function() { - g.bind(0); - c.uniforms({u_texture:0, u_aberration:f, u_distortion:k, u_blur:l}).draw(d); - }); - this.setOutputData(0, this._tex); - } - } - }; - q.pixel_shader = "precision highp float;\n\r\n\t\t\tprecision highp float;\n\r\n\t\t\tvarying vec2 v_coord;\n\r\n\t\t\tuniform sampler2D u_texture;\n\r\n\t\t\tuniform vec2 u_camera_planes;\n\r\n\t\t\tuniform float u_aberration;\n\r\n\t\t\tuniform float u_distortion;\n\r\n\t\t\tuniform float u_blur;\n\r\n\t\t\t\n\r\n\t\t\tvoid main() {\n\r\n\t\t\t\tvec2 coord = v_coord;\n\r\n\t\t\t\tfloat dist = distance(vec2(0.5), coord);\n\r\n\t\t\t\tvec2 dist_coord = coord - vec2(0.5);\n\r\n\t\t\t\tfloat percent = 1.0 + ((0.5 - dist) / 0.5) * u_distortion;\n\r\n\t\t\t\tdist_coord *= percent;\n\r\n\t\t\t\tcoord = dist_coord + vec2(0.5);\n\r\n\t\t\t\tvec4 color = texture2D(u_texture,coord, u_blur * dist);\n\r\n\t\t\t\tcolor.r = texture2D(u_texture,vec2(0.5) + dist_coord * (1.0+0.01*u_aberration), u_blur * dist ).r;\n\r\n\t\t\t\tcolor.b = texture2D(u_texture,vec2(0.5) + dist_coord * (1.0-0.01*u_aberration), u_blur * dist ).b;\n\r\n\t\t\t\tgl_FragColor = color;\n\r\n\t\t\t}\n\r\n\t\t\t"; - f.registerNodeType("fx/lens", q); - u.LGraphFXLens = q; - g.title = "Bokeh"; - g.desc = "applies an Bokeh effect"; - g.widgets_info = {shape:{widget:"texture"}}; - g.prototype.onExecute = function() { - var f = this.getInputData(0), k = this.getInputData(1), n = this.getInputData(2); - if (f && n && this.properties.shape) { - k || (k = f); - var l = LGraphTexture.getTexture(this.properties.shape); - if (l) { - var d = this.properties.threshold; - this.isInputConnected(3) && (d = this.getInputData(3), this.properties.threshold = d); - var c = gl.UNSIGNED_BYTE; - this.properties.high_precision && (c = gl.half_float_ext ? gl.HALF_FLOAT_OES : gl.FLOAT); - this._temp_texture && this._temp_texture.type == c && this._temp_texture.width == f.width && this._temp_texture.height == f.height || (this._temp_texture = new GL.Texture(f.width, f.height, {type:c, format:gl.RGBA, filter:gl.LINEAR})); - var a = g._first_shader; - a || (a = g._first_shader = new GL.Shader(Shader.SCREEN_VERTEX_SHADER, g._first_pixel_shader)); - var b = g._second_shader; - b || (b = g._second_shader = new GL.Shader(g._second_vertex_shader, g._second_pixel_shader)); - var e = this._points_mesh; - e && e._width == f.width && e._height == f.height && 2 == e._spacing || (e = this.createPointsMesh(f.width, f.height, 2)); - var q = Mesh.getScreenQuad(), h = this.properties.size, m = this.properties.alpha; - gl.disable(gl.DEPTH_TEST); - gl.disable(gl.BLEND); - this._temp_texture.drawTo(function() { - f.bind(0); - k.bind(1); - n.bind(2); - a.uniforms({u_texture:0, u_texture_blur:1, u_mask:2, u_texsize:[f.width, f.height]}).draw(q); - }); - this._temp_texture.drawTo(function() { - gl.enable(gl.BLEND); - gl.blendFunc(gl.ONE, gl.ONE); - f.bind(0); - l.bind(3); - b.uniforms({u_texture:0, u_mask:2, u_shape:3, u_alpha:m, u_threshold:d, u_pointSize:h, u_itexsize:[1.0 / f.width, 1.0 / f.height]}).draw(e, gl.POINTS); - }); - this.setOutputData(0, this._temp_texture); - } - } else { - this.setOutputData(0, f); - } - }; - g.prototype.createPointsMesh = function(g, f, k) { - for (var l = Math.round(g / k), d = Math.round(f / k), c = new Float32Array(l * d * 2), a = -1, b = 2 / g * k, e = 2 / f * k, n = 0; n < d; ++n) { - for (var h = -1, m = 0; m < l; ++m) { - var p = n * l * 2 + 2 * m; - c[p] = h; - c[p + 1] = a; - h += b; - } - a += e; - } - this._points_mesh = GL.Mesh.load({vertices2D:c}); - this._points_mesh._width = g; - this._points_mesh._height = f; - this._points_mesh._spacing = k; - return this._points_mesh; - }; - g._first_pixel_shader = "precision highp float;\n\r\n\t\t\tprecision highp float;\n\r\n\t\t\tvarying vec2 v_coord;\n\r\n\t\t\tuniform sampler2D u_texture;\n\r\n\t\t\tuniform sampler2D u_texture_blur;\n\r\n\t\t\tuniform sampler2D u_mask;\n\r\n\t\t\t\n\r\n\t\t\tvoid main() {\n\r\n\t\t\t\tvec4 color = texture2D(u_texture, v_coord);\n\r\n\t\t\t\tvec4 blurred_color = texture2D(u_texture_blur, v_coord);\n\r\n\t\t\t\tfloat mask = texture2D(u_mask, v_coord).x;\n\r\n\t\t\t gl_FragColor = mix(color, blurred_color, mask);\n\r\n\t\t\t}\n\r\n\t\t\t"; - g._second_vertex_shader = "precision highp float;\n\r\n\t\t\tattribute vec2 a_vertex2D;\n\r\n\t\t\tvarying vec4 v_color;\n\r\n\t\t\tuniform sampler2D u_texture;\n\r\n\t\t\tuniform sampler2D u_mask;\n\r\n\t\t\tuniform vec2 u_itexsize;\n\r\n\t\t\tuniform float u_pointSize;\n\r\n\t\t\tuniform float u_threshold;\n\r\n\t\t\tvoid main() {\n\r\n\t\t\t\tvec2 coord = a_vertex2D * 0.5 + 0.5;\n\r\n\t\t\t\tv_color = texture2D( u_texture, coord );\n\r\n\t\t\t\tv_color += texture2D( u_texture, coord + vec2(u_itexsize.x, 0.0) );\n\r\n\t\t\t\tv_color += texture2D( u_texture, coord + vec2(0.0, u_itexsize.y));\n\r\n\t\t\t\tv_color += texture2D( u_texture, coord + u_itexsize);\n\r\n\t\t\t\tv_color *= 0.25;\n\r\n\t\t\t\tfloat mask = texture2D(u_mask, coord).x;\n\r\n\t\t\t\tfloat luminance = length(v_color) * mask;\n\r\n\t\t\t\t/*luminance /= (u_pointSize*u_pointSize)*0.01 */;\n\r\n\t\t\t\tluminance -= u_threshold;\n\r\n\t\t\t\tif(luminance < 0.0)\n\r\n\t\t\t\t{\n\r\n\t\t\t\t\tgl_Position.x = -100.0;\n\r\n\t\t\t\t\treturn;\n\r\n\t\t\t\t}\n\r\n\t\t\t\tgl_PointSize = u_pointSize;\n\r\n\t\t\t\tgl_Position = vec4(a_vertex2D,0.0,1.0);\n\r\n\t\t\t}\n\r\n\t\t\t"; - g._second_pixel_shader = "precision highp float;\n\r\n\t\t\tvarying vec4 v_color;\n\r\n\t\t\tuniform sampler2D u_shape;\n\r\n\t\t\tuniform float u_alpha;\n\r\n\t\t\t\n\r\n\t\t\tvoid main() {\n\r\n\t\t\t\tvec4 color = texture2D( u_shape, gl_PointCoord );\n\r\n\t\t\t\tcolor *= v_color * u_alpha;\n\r\n\t\t\t\tgl_FragColor = color;\n\r\n\t\t\t}\n"; - f.registerNodeType("fx/bokeh", g); - u.LGraphFXBokeh = g; - n.title = "FX"; - n.desc = "applies an FX from a list"; - n.widgets_info = {fx:{widget:"combo", values:["halftone", "pixelate", "lowpalette", "noise", "gamma"]}, precision:{widget:"combo", values:LGraphTexture.MODE_VALUES}}; - n.shaders = {}; - n.prototype.onExecute = function() { - if (this.isOutputConnected(0)) { - var g = this.getInputData(0); - if (this.properties.precision === LGraphTexture.PASS_THROUGH) { - this.setOutputData(0, g); - } else { - if (g) { - this._tex = LGraphTexture.getTargetTexture(g, this._tex, this.properties.precision); - var f = this.properties.value1; - this.isInputConnected(1) && (f = this.getInputData(1), this.properties.value1 = f); - var k = this.properties.value2; - this.isInputConnected(2) && (k = this.getInputData(2), this.properties.value2 = k); - var l = this.properties.fx, d = n.shaders[l]; - if (!d) { - var c = n["pixel_shader_" + l]; - if (!c) { - return; - } - d = n.shaders[l] = new GL.Shader(Shader.SCREEN_VERTEX_SHADER, c); - } - gl.disable(gl.BLEND); - gl.disable(gl.DEPTH_TEST); - var a = Mesh.getScreenQuad(); - camera_planes = u.LS && LS.Renderer._current_camera ? [LS.Renderer._current_camera.near, LS.Renderer._current_camera.far] : [1, 100]; - var b = null; - "noise" == l && (b = LGraphTexture.getNoiseTexture()); - this._tex.drawTo(function() { - g.bind(0); - "noise" == l && b.bind(1); - d.uniforms({u_texture:0, u_noise:1, u_size:[g.width, g.height], u_rand:[Math.random(), Math.random()], u_value1:f, u_value2:k, u_camera_planes:camera_planes}).draw(a); - }); - this.setOutputData(0, this._tex); - } - } - } - }; - n.pixel_shader_halftone = "precision highp float;\n\r\n\t\t\tvarying vec2 v_coord;\n\r\n\t\t\tuniform sampler2D u_texture;\n\r\n\t\t\tuniform vec2 u_camera_planes;\n\r\n\t\t\tuniform vec2 u_size;\n\r\n\t\t\tuniform float u_value1;\n\r\n\t\t\tuniform float u_value2;\n\r\n\t\t\t\n\r\n\t\t\tfloat pattern() {\n\r\n\t\t\t\tfloat s = sin(u_value1 * 3.1415), c = cos(u_value1 * 3.1415);\n\r\n\t\t\t\tvec2 tex = v_coord * u_size.xy;\n\r\n\t\t\t\tvec2 point = vec2(\n\r\n\t\t\t\t c * tex.x - s * tex.y ,\n\r\n\t\t\t\t s * tex.x + c * tex.y \n\r\n\t\t\t\t) * u_value2;\n\r\n\t\t\t\treturn (sin(point.x) * sin(point.y)) * 4.0;\n\r\n\t\t\t}\n\r\n\t\t\tvoid main() {\n\r\n\t\t\t\tvec4 color = texture2D(u_texture, v_coord);\n\r\n\t\t\t\tfloat average = (color.r + color.g + color.b) / 3.0;\n\r\n\t\t\t\tgl_FragColor = vec4(vec3(average * 10.0 - 5.0 + pattern()), color.a);\n\r\n\t\t\t}\n"; - n.pixel_shader_pixelate = "precision highp float;\n\r\n\t\t\tvarying vec2 v_coord;\n\r\n\t\t\tuniform sampler2D u_texture;\n\r\n\t\t\tuniform vec2 u_camera_planes;\n\r\n\t\t\tuniform vec2 u_size;\n\r\n\t\t\tuniform float u_value1;\n\r\n\t\t\tuniform float u_value2;\n\r\n\t\t\t\n\r\n\t\t\tvoid main() {\n\r\n\t\t\t\tvec2 coord = vec2( floor(v_coord.x * u_value1) / u_value1, floor(v_coord.y * u_value2) / u_value2 );\n\r\n\t\t\t\tvec4 color = texture2D(u_texture, coord);\n\r\n\t\t\t\tgl_FragColor = color;\n\r\n\t\t\t}\n"; - n.pixel_shader_lowpalette = "precision highp float;\n\r\n\t\t\tvarying vec2 v_coord;\n\r\n\t\t\tuniform sampler2D u_texture;\n\r\n\t\t\tuniform vec2 u_camera_planes;\n\r\n\t\t\tuniform vec2 u_size;\n\r\n\t\t\tuniform float u_value1;\n\r\n\t\t\tuniform float u_value2;\n\r\n\t\t\t\n\r\n\t\t\tvoid main() {\n\r\n\t\t\t\tvec4 color = texture2D(u_texture, v_coord);\n\r\n\t\t\t\tgl_FragColor = floor(color * u_value1) / u_value1;\n\r\n\t\t\t}\n"; - n.pixel_shader_noise = "precision highp float;\n\r\n\t\t\tvarying vec2 v_coord;\n\r\n\t\t\tuniform sampler2D u_texture;\n\r\n\t\t\tuniform sampler2D u_noise;\n\r\n\t\t\tuniform vec2 u_size;\n\r\n\t\t\tuniform float u_value1;\n\r\n\t\t\tuniform float u_value2;\n\r\n\t\t\tuniform vec2 u_rand;\n\r\n\t\t\t\n\r\n\t\t\tvoid main() {\n\r\n\t\t\t\tvec4 color = texture2D(u_texture, v_coord);\n\r\n\t\t\t\tvec3 noise = texture2D(u_noise, v_coord * vec2(u_size.x / 512.0, u_size.y / 512.0) + u_rand).xyz - vec3(0.5);\n\r\n\t\t\t\tgl_FragColor = vec4( color.xyz + noise * u_value1, color.a );\n\r\n\t\t\t}\n"; - n.pixel_shader_gamma = "precision highp float;\n\r\n\t\t\tvarying vec2 v_coord;\n\r\n\t\t\tuniform sampler2D u_texture;\n\r\n\t\t\tuniform float u_value1;\n\r\n\t\t\t\n\r\n\t\t\tvoid main() {\n\r\n\t\t\t\tvec4 color = texture2D(u_texture, v_coord);\n\r\n\t\t\t\tfloat gamma = 1.0 / u_value1;\n\r\n\t\t\t\tgl_FragColor = vec4( pow( color.xyz, vec3(gamma) ), color.a );\n\r\n\t\t\t}\n"; - f.registerNodeType("fx/generic", n); - u.LGraphFXGeneric = n; - k.title = "Vigneting"; - k.desc = "Vigneting"; - k.widgets_info = {precision:{widget:"combo", values:LGraphTexture.MODE_VALUES}}; - k.prototype.onExecute = function() { - var g = this.getInputData(0); - if (this.properties.precision === LGraphTexture.PASS_THROUGH) { - this.setOutputData(0, g); - } else { - if (g) { - this._tex = LGraphTexture.getTargetTexture(g, this._tex, this.properties.precision); - var f = this.properties.intensity; - this.isInputConnected(1) && (f = this.getInputData(1), this.properties.intensity = f); - gl.disable(gl.BLEND); - gl.disable(gl.DEPTH_TEST); - var n = Mesh.getScreenQuad(), l = k._shader, d = this.properties.invert; - this._tex.drawTo(function() { - g.bind(0); - l.uniforms({u_texture:0, u_intensity:f, u_isize:[1 / g.width, 1 / g.height], u_invert:d ? 1 : 0}).draw(n); - }); - this.setOutputData(0, this._tex); - } - } - }; - k.pixel_shader = "precision highp float;\n\r\n\t\t\tprecision highp float;\n\r\n\t\t\tvarying vec2 v_coord;\n\r\n\t\t\tuniform sampler2D u_texture;\n\r\n\t\t\tuniform float u_intensity;\n\r\n\t\t\tuniform int u_invert;\n\r\n\t\t\t\n\r\n\t\t\tvoid main() {\n\r\n\t\t\t\tfloat luminance = 1.0 - length( v_coord - vec2(0.5) ) * 1.414;\n\r\n\t\t\t\tvec4 color = texture2D(u_texture, v_coord);\n\r\n\t\t\t\tif(u_invert == 1)\n\r\n\t\t\t\t\tluminance = 1.0 - luminance;\n\r\n\t\t\t\tluminance = mix(1.0, luminance, u_intensity);\n\r\n\t\t\t gl_FragColor = vec4( luminance * color.xyz, color.a);\n\r\n\t\t\t}\n\r\n\t\t\t"; - f.registerNodeType("fx/vigneting", k); - u.LGraphFXVigneting = k; - } -})(this); -(function(u) { - function f(d) { - this.cmd = this.channel = 0; - d ? this.setup(d) : this.data = [0, 0, 0]; - } - function k(d, c) { - navigator.requestMIDIAccess ? (this.on_ready = d, this.state = {note:[], cc:[]}, navigator.requestMIDIAccess().then(this.onMIDISuccess.bind(this), this.onMIDIFailure.bind(this))) : (this.error = "not suppoorted", c ? c("Not supported") : console.error("MIDI NOT SUPPORTED, enable by chrome://flags")); - } - function n() { - this.addOutput("on_midi", l.EVENT); - this.addOutput("out", "midi"); - this.properties = {port:0}; - this._current_midi_event = this._last_midi_event = null; - var d = this; - new k(function(c) { - d._midi = c; - if (d._waiting) { - d.onStart(); - } - d._waiting = !1; - }); - } - function g() { - this.addInput("send", l.EVENT); - this.properties = {port:0}; - var d = this; - new k(function(c) { - d._midi = c; - }); - } - function q() { - this.addInput("on_midi", l.EVENT); - this._str = ""; - this.size = [200, 40]; - } - function r() { - this.properties = {channel:-1, cmd:-1, min_value:-1, max_value:-1}; - this.addInput("in", l.EVENT); - this.addOutput("on_midi", l.EVENT); - } - function v() { - this.properties = {channel:0, cmd:"CC", value1:1, value2:1}; - this.addInput("send", l.EVENT); - this.addInput("assign", l.EVENT); - this.addOutput("on_midi", l.EVENT); - } - function w() { - this.properties = {cc:1, value:0}; - this.addOutput("value", "number"); - } - var l = u.LiteGraph; - f.prototype.setup = function(d) { - this.data = d; - this.status = d = d[0]; - var c = d & 240; - this.cmd = 240 <= d ? d : c; - this.cmd == f.NOTEON && 0 == this.velocity && (this.cmd = f.NOTEOFF); - this.cmd_str = f.commands[this.cmd] || ""; - if (c >= f.NOTEON || c <= f.NOTEOFF) { - this.channel = d & 15; - } - }; - Object.defineProperty(f.prototype, "velocity", {get:function() { - return this.cmd == f.NOTEON ? this.data[2] : -1; - }, set:function(d) { - this.data[2] = d; - }, enumerable:!0}); - f.notes = "A A# B C C# D D# E F F# G G#".split(" "); - f.prototype.getPitch = function() { - return 440 * Math.pow(2, (this.data[1] - 69) / 12); - }; - f.computePitch = function(d) { - return 440 * Math.pow(2, (d - 69) / 12); - }; - f.prototype.getCC = function() { - return this.data[1]; - }; - f.prototype.getCCValue = function() { - return this.data[2]; - }; - f.prototype.getPitchBend = function() { - return this.data[1] + (this.data[2] << 7) - 8192; - }; - f.computePitchBend = function(d, c) { - return d + (c << 7) - 8192; - }; - f.prototype.setCommandFromString = function(d) { - this.cmd = f.computeCommandFromString(d); - }; - f.computeCommandFromString = function(d) { - if (!d) { - return 0; - } - if (d && d.constructor === Number) { - return d; - } - d = d.toUpperCase(); - switch(d) { - case "NOTE ON": - case "NOTEON": - return f.NOTEON; - case "NOTE OFF": - case "NOTEOFF": - return f.NOTEON; - case "KEY PRESSURE": - case "KEYPRESSURE": - return f.KEYPRESSURE; - case "CONTROLLER CHANGE": - case "CONTROLLERCHANGE": - case "CC": - return f.CONTROLLERCHANGE; - case "PROGRAM CHANGE": - case "PROGRAMCHANGE": - case "PC": - return f.PROGRAMCHANGE; - case "CHANNEL PRESSURE": - case "CHANNELPRESSURE": - return f.CHANNELPRESSURE; - case "PITCH BEND": - case "PITCHBEND": - return f.PITCHBEND; - case "TIME TICK": - case "TIMETICK": - return f.TIMETICK; - default: - return Number(d); - } - }; - f.toNoteString = function(d) { - var c = (d - 21) % 12; - 0 > c && (c = 12 + c); - return f.notes[c] + Math.floor((d - 24) / 12 + 1); - }; - f.prototype.toString = function() { - var d = "" + this.channel + ". "; - switch(this.cmd) { - case f.NOTEON: - d += "NOTEON " + f.toNoteString(this.data[1]); - break; - case f.NOTEOFF: - d += "NOTEOFF " + f.toNoteString(this.data[1]); - break; - case f.CONTROLLERCHANGE: - d += "CC " + this.data[1] + " " + this.data[2]; - break; - case f.PROGRAMCHANGE: - d += "PC " + this.data[1]; - break; - case f.PITCHBEND: - d += "PITCHBEND " + this.getPitchBend(); - break; - case f.KEYPRESSURE: - d += "KEYPRESS " + this.data[1]; - } - return d; - }; - f.prototype.toHexString = function() { - for (var d = "", c = 0; c < this.data.length; c++) { - d += this.data[c].toString(16) + " "; - } - }; - f.NOTEOFF = 128; - f.NOTEON = 144; - f.KEYPRESSURE = 160; - f.CONTROLLERCHANGE = 176; - f.PROGRAMCHANGE = 192; - f.CHANNELPRESSURE = 208; - f.PITCHBEND = 224; - f.TIMETICK = 248; - f.commands = {128:"note off", 144:"note on", 160:"key pressure", 176:"controller change", 192:"program change", 208:"channel pressure", 224:"pitch bend", 240:"system", 242:"Song pos", 243:"Song select", 246:"Tune request", 248:"time tick", 250:"Start Song", 251:"Continue Song", 252:"Stop Song", 254:"Sensing", 255:"Reset"}; - k.input = null; - k.MIDIEvent = f; - k.prototype.onMIDISuccess = function(d) { - console.log("MIDI ready!"); - console.log(d); - this.midi = d; - this.updatePorts(); - if (this.on_ready) { - this.on_ready(this); - } - }; - k.prototype.updatePorts = function() { - var d = this.midi; - this.input_ports = d.inputs; - for (var c = 0, a = this.input_ports.values(), b = a.next(); b && !1 === b.done;) { - b = b.value, console.log("Input port [type:'" + b.type + "'] id:'" + b.id + "' manufacturer:'" + b.manufacturer + "' name:'" + b.name + "' version:'" + b.version + "'"), c++, b = a.next(); - } - this.num_input_ports = c; - c = 0; - this.output_ports = d.outputs; - a = this.output_ports.values(); - for (b = a.next(); b && !1 === b.done;) { - b = b.value, console.log("Output port [type:'" + b.type + "'] id:'" + b.id + "' manufacturer:'" + b.manufacturer + "' name:'" + b.name + "' version:'" + b.version + "'"), c++, b = a.next(); - } - this.num_output_ports = c; - }; - k.prototype.onMIDIFailure = function(d) { - console.error("Failed to get MIDI access - " + d); - }; - k.prototype.openInputPort = function(d, c) { - d = this.input_ports.get("input-" + d); - if (!d) { - return !1; - } - k.input = this; - var a = this; - d.onmidimessage = function(b) { - var d = new f(b.data); - a.updateState(d); - c && c(b.data, d); - if (k.on_message) { - k.on_message(b.data, d); - } - }; - console.log("port open: ", d); - return !0; - }; - k.parseMsg = function(d) { - }; - k.prototype.updateState = function(d) { - switch(d.cmd) { - case f.NOTEON: - this.state.note[d.value1 | 0] = d.value2; - break; - case f.NOTEOFF: - this.state.note[d.value1 | 0] = 0; - break; - case f.CONTROLLERCHANGE: - this.state.cc[d.getCC()] = d.getCCValue(); - } - }; - k.prototype.sendMIDI = function(d, c) { - c && (d = this.output_ports.get("output-" + d)) && (k.output = this, c.constructor === f ? d.send(c.data) : d.send(c)); - }; - n.MIDIInterface = k; - n.title = "MIDI Input"; - n.desc = "Reads MIDI from a input port"; - n.prototype.getPropertyInfo = function(d) { - if (this._midi && "port" == d) { - d = {}; - for (var c = 0; c < this._midi.input_ports.size; ++c) { - var a = this._midi.input_ports.get("input-" + c); - d[c] = c + ".- " + a.name + " version:" + a.version; - } - return {type:"enum", values:d}; - } - }; - n.prototype.onStart = function() { - this._midi ? this._midi.openInputPort(this.properties.port, this.onMIDIEvent.bind(this)) : this._waiting = !0; - }; - n.prototype.onMIDIEvent = function(d, c) { - this._last_midi_event = c; - this.trigger("on_midi", c); - c.cmd == f.NOTEON ? this.trigger("on_noteon", c) : c.cmd == f.NOTEOFF ? this.trigger("on_noteoff", c) : c.cmd == f.CONTROLLERCHANGE ? this.trigger("on_cc", c) : c.cmd == f.PROGRAMCHANGE ? this.trigger("on_pc", c) : c.cmd == f.PITCHBEND && this.trigger("on_pitchbend", c); - }; - n.prototype.onExecute = function() { - if (this.outputs) { - for (var d = this._last_midi_event, c = 0; c < this.outputs.length; ++c) { - switch(this.outputs[c].name) { - case "midi": - var a = this._midi; - break; - case "last_midi": - a = d; - break; - default: - continue; - } - this.setOutputData(c, a); - } - } - }; - n.prototype.onGetOutputs = function() { - return [["last_midi", "midi"], ["on_midi", l.EVENT], ["on_noteon", l.EVENT], ["on_noteoff", l.EVENT], ["on_cc", l.EVENT], ["on_pc", l.EVENT], ["on_pitchbend", l.EVENT]]; - }; - l.registerNodeType("midi/input", n); - g.MIDIInterface = k; - g.title = "MIDI Output"; - g.desc = "Sends MIDI to output channel"; - g.prototype.getPropertyInfo = function(d) { - if (this._midi && "port" == d) { - d = {}; - for (var c = 0; c < this._midi.output_ports.size; ++c) { - var a = this._midi.output_ports.get(c); - d[c] = c + ".- " + a.name + " version:" + a.version; - } - return {type:"enum", values:d}; - } - }; - g.prototype.onAction = function(d, c) { - console.log(c); - this._midi && ("send" == d && this._midi.sendMIDI(this.port, c), this.trigger("midi", c)); - }; - g.prototype.onGetInputs = function() { - return [["send", l.ACTION]]; - }; - g.prototype.onGetOutputs = function() { - return [["on_midi", l.EVENT]]; - }; - l.registerNodeType("midi/output", g); - q.title = "MIDI Show"; - q.desc = "Shows MIDI in the graph"; - q.prototype.onAction = function(d, c) { - c && (this._str = c.constructor === f ? c.toString() : "???"); - }; - q.prototype.onDrawForeground = function(d) { - this._str && (d.font = "30px Arial", d.fillText(this._str, 10, 0.8 * this.size[1])); - }; - q.prototype.onGetInputs = function() { - return [["in", l.ACTION]]; - }; - q.prototype.onGetOutputs = function() { - return [["on_midi", l.EVENT]]; - }; - l.registerNodeType("midi/show", q); - r.title = "MIDI Filter"; - r.desc = "Filters MIDI messages"; - r.prototype.onAction = function(d, c) { - !c || c.constructor !== f || -1 != this.properties.channel && c.channel != this.properties.channel || -1 != this.properties.cmd && c.cmd != this.properties.cmd || -1 != this.properties.min_value && c.data[1] < this.properties.min_value || -1 != this.properties.max_value && c.data[1] > this.properties.max_value || this.trigger("on_midi", c); - }; - l.registerNodeType("midi/filter", r); - v.title = "MIDIEvent"; - v.desc = "Create a MIDI Event"; - v.prototype.onAction = function(d, c) { - "assign" == d ? (this.properties.channel = c.channel, this.properties.cmd = c.cmd, this.properties.value1 = c.data[1], this.properties.value2 = c.data[2]) : (c = new f, c.channel = this.properties.channel, this.properties.cmd && this.properties.cmd.constructor === String ? c.setCommandFromString(this.properties.cmd) : c.cmd = this.properties.cmd, c.data[0] = c.cmd | c.channel, c.data[1] = Number(this.properties.value1), c.data[2] = Number(this.properties.value2), this.trigger("on_midi", c)); - }; - v.prototype.onExecute = function() { - var d = this.properties; - if (this.outputs) { - for (var c = 0; c < this.outputs.length; ++c) { - switch(this.outputs[c].name) { - case "midi": - var a = new f; - a.setup([d.cmd, d.value1, d.value2]); - a.channel = d.channel; - break; - case "command": - a = d.cmd; - break; - case "cc": - a = d.value1; - break; - case "cc_value": - a = d.value2; - break; - case "note": - a = d.cmd == f.NOTEON || d.cmd == f.NOTEOFF ? d.value1 : null; - break; - case "velocity": - a = d.cmd == f.NOTEON ? d.value2 : null; - break; - case "pitch": - a = d.cmd == f.NOTEON ? f.computePitch(d.value1) : null; - break; - case "pitchbend": - a = d.cmd == f.PITCHBEND ? f.computePitchBend(d.value1, d.value2) : null; - break; - default: - continue; - } - null !== a && this.setOutputData(c, a); - } - } - }; - v.prototype.onPropertyChanged = function(d, c) { - "cmd" == d && (this.properties.cmd = f.computeCommandFromString(c)); - }; - v.prototype.onGetOutputs = function() { - return [["midi", "midi"], ["on_midi", l.EVENT], ["command", "number"], ["note", "number"], ["velocity", "number"], ["cc", "number"], ["cc_value", "number"], ["pitch", "number"], ["pitchbend", "number"]]; - }; - l.registerNodeType("midi/event", v); - w.title = "MIDICC"; - w.desc = "gets a Controller Change"; - w.prototype.onExecute = function() { - k.input && (this.properties.value = k.input.state.cc[this.properties.cc]); - this.setOutputData(0, this.properties.value); - }; - l.registerNodeType("midi/cc", w); -})(this); -(function(u) { - function f() { - this.properties = {src:"", gain:0.5, loop:!0, autoplay:!0, playbackRate:1}; - this._loading_audio = !1; - this._audiobuffer = null; - this._audionodes = []; - this._last_sourcenode = null; - this.addOutput("out", "audio"); - this.addInput("gain", "number"); - this.audionode = h.getAudioContext().createGain(); - this.audionode.graphnode = this; - this.audionode.gain.value = this.properties.gain; - this.properties.src && this.loadSound(this.properties.src); - } - function k() { - this.properties = {fftSize:2048, minDecibels:-100, maxDecibels:-10, smoothingTimeConstant:0.5}; - this.audionode = h.getAudioContext().createAnalyser(); - this.audionode.graphnode = this; - this.audionode.fftSize = this.properties.fftSize; - this.audionode.minDecibels = this.properties.minDecibels; - this.audionode.maxDecibels = this.properties.maxDecibels; - this.audionode.smoothingTimeConstant = this.properties.smoothingTimeConstant; - this.addInput("in", "audio"); - this.addOutput("freqs", "array"); - this.addOutput("samples", "array"); - this._time_bin = this._freq_bin = null; - } - function n() { - this.properties = {gain:1}; - this.audionode = h.getAudioContext().createGain(); - this.addInput("in", "audio"); - this.addInput("gain", "number"); - this.addOutput("out", "audio"); - } - function g() { - this.properties = {impulse_src:"", normalize:!0}; - this.audionode = h.getAudioContext().createConvolver(); - this.addInput("in", "audio"); - this.addOutput("out", "audio"); - } - function q() { - this.properties = {threshold:-50, knee:40, ratio:12, reduction:-20, attack:0, release:0.25}; - this.audionode = h.getAudioContext().createDynamicsCompressor(); - this.addInput("in", "audio"); - this.addOutput("out", "audio"); - } - function r() { - this.properties = {}; - this.audionode = h.getAudioContext().createWaveShaper(); - this.addInput("in", "audio"); - this.addInput("shape", "waveshape"); - this.addOutput("out", "audio"); - } - function v() { - this.properties = {gain1:0.5, gain2:0.5}; - this.audionode = h.getAudioContext().createGain(); - this.audionode1 = h.getAudioContext().createGain(); - this.audionode1.gain.value = this.properties.gain1; - this.audionode2 = h.getAudioContext().createGain(); - this.audionode2.gain.value = this.properties.gain2; - this.audionode1.connect(this.audionode); - this.audionode2.connect(this.audionode); - this.addInput("in1", "audio"); - this.addInput("in1 gain", "number"); - this.addInput("in2", "audio"); - this.addInput("in2 gain", "number"); - this.addOutput("out", "audio"); - } - function w() { - this.properties = {delayTime:0.5}; - this.audionode = h.getAudioContext().createDelay(10); - this.audionode.delayTime.value = this.properties.delayTime; - this.addInput("in", "audio"); - this.addInput("time", "number"); - this.addOutput("out", "audio"); - } - function l() { - this.properties = {frequency:350, detune:0, Q:1}; - this.addProperty("type", "lowpass", "enum", {values:"lowpass highpass bandpass lowshelf highshelf peaking notch allpass".split(" ")}); - this.audionode = h.getAudioContext().createBiquadFilter(); - this.addInput("in", "audio"); - this.addOutput("out", "audio"); - } - function d() { - this.properties = {frequency:440, detune:0, type:"sine"}; - this.addProperty("type", "sine", "enum", {values:["sine", "square", "sawtooth", "triangle", "custom"]}); - this.audionode = h.getAudioContext().createOscillator(); - this.addOutput("out", "audio"); - } - function c() { - this.properties = {continuous:!0, mark:-1}; - this.addInput("data", "array"); - this.addInput("mark", "number"); - this.size = [300, 200]; - this._last_buffer = null; - } - function a() { - this.properties = {band:440, amplitude:1}; - this.addInput("freqs", "array"); - this.addOutput("signal", "number"); - } - function b() { - if (!b.default_code) { - var a = b.default_function.toString(), c = a.indexOf("{") + 1, d = a.lastIndexOf("}"); - b.default_code = a.substr(c, d - c); - } - this.properties = {code:b.default_code}; - a = h.getAudioContext(); - a.createScriptProcessor ? this.audionode = a.createScriptProcessor(4096, 1, 1) : (console.warn("ScriptProcessorNode deprecated"), this.audionode = a.createGain()); - this.processCode(); - b._bypass_function || (b._bypass_function = this.audionode.onaudioprocess); - this.addInput("in", "audio"); - this.addOutput("out", "audio"); - } - function e() { - this.audionode = h.getAudioContext().destination; - this.addInput("in", "audio"); - } - var y = u.LiteGraph, h = {}; - u.LGAudio = h; - h.getAudioContext = function() { - if (!this._audio_context) { - window.AudioContext = window.AudioContext || window.webkitAudioContext; - if (!window.AudioContext) { - return console.error("AudioContext not supported by browser"), null; - } - this._audio_context = new AudioContext; - this._audio_context.onmessage = function(a) { - console.log("msg", a); - }; - this._audio_context.onended = function(a) { - console.log("ended", a); - }; - this._audio_context.oncomplete = function(a) { - console.log("complete", a); - }; - } - return this._audio_context; - }; - h.connect = function(a, b) { - try { - a.connect(b); - } catch (B) { - console.warn("LGraphAudio:", B); - } - }; - h.disconnect = function(a, b) { - try { - a.disconnect(b); - } catch (B) { - console.warn("LGraphAudio:", B); - } - }; - h.changeAllAudiosConnections = function(a, b) { - if (a.inputs) { - for (var c = 0; c < a.inputs.length; ++c) { - var d = a.graph.links[a.inputs[c].link]; - if (d) { - var e = a.graph.getNodeById(d.origin_id); - e = e.getAudioNodeInOutputSlot ? e.getAudioNodeInOutputSlot(d.origin_slot) : e.audionode; - d = a.getAudioNodeInInputSlot ? a.getAudioNodeInInputSlot(c) : a.audionode; - b ? h.connect(e, d) : h.disconnect(e, d); - } - } - } - if (a.outputs) { - for (c = 0; c < a.outputs.length; ++c) { - for (var g = a.outputs[c], f = 0; f < g.links.length; ++f) { - if (d = a.graph.links[g.links[f]]) { - e = a.getAudioNodeInOutputSlot ? a.getAudioNodeInOutputSlot(c) : a.audionode; - var m = a.graph.getNodeById(d.target_id); - d = m.getAudioNodeInInputSlot ? m.getAudioNodeInInputSlot(d.target_slot) : m.audionode; - b ? h.connect(e, d) : h.disconnect(e, d); - } - } - } - } - }; - h.onConnectionsChange = function(a, b, c, d) { - a == y.OUTPUT && (a = null, d && (a = this.graph.getNodeById(d.target_id)), a && (b = this.getAudioNodeInOutputSlot ? this.getAudioNodeInOutputSlot(b) : this.audionode, d = a.getAudioNodeInInputSlot ? a.getAudioNodeInInputSlot(d.target_slot) : a.audionode, c ? h.connect(b, d) : h.disconnect(b, d))); - }; - h.createAudioNodeWrapper = function(a) { - var b = a.prototype.onPropertyChanged; - a.prototype.onPropertyChanged = function(a, c) { - b && b.call(this, a, c); - this.audionode && void 0 !== this.audionode[a] && (void 0 !== this.audionode[a].value ? this.audionode[a].value = c : this.audionode[a] = c); - }; - a.prototype.onConnectionsChange = h.onConnectionsChange; - }; - h.cached_audios = {}; - h.loadSound = function(a, b, c) { - function d(a) { - console.log("Audio loading sample error:", a); - c && c(a); - } - if (h.cached_audios[a] && -1 == a.indexOf("blob:")) { - b && b(h.cached_audios[a]); - } else { - h.onProcessAudioURL && (a = h.onProcessAudioURL(a)); - var e = new XMLHttpRequest; - e.open("GET", a, !0); - e.responseType = "arraybuffer"; - var g = h.getAudioContext(); - e.onload = function() { - console.log("AudioSource loaded"); - g.decodeAudioData(e.response, function(c) { - console.log("AudioSource decoded"); - h.cached_audios[a] = c; - b && b(c); - }, d); - }; - e.send(); - return e; - } - }; - f["@src"] = {widget:"resource"}; - f.supported_extensions = ["wav", "ogg", "mp3"]; - f.prototype.onAdded = function(a) { - if (a.status === LGraph.STATUS_RUNNING) { - this.onStart(); - } - }; - f.prototype.onStart = function() { - this._audiobuffer && this.properties.autoplay && this.playBuffer(this._audiobuffer); - }; - f.prototype.onStop = function() { - this.stopAllSounds(); - }; - f.prototype.onPause = function() { - this.pauseAllSounds(); - }; - f.prototype.onUnpause = function() { - this.unpauseAllSounds(); - }; - f.prototype.onRemoved = function() { - this.stopAllSounds(); - this._dropped_url && URL.revokeObjectURL(this._url); - }; - f.prototype.stopAllSounds = function() { - for (var a = 0; a < this._audionodes.length; ++a) { - this._audionodes[a].started && (this._audionodes[a].started = !1, this._audionodes[a].stop()); - } - this._audionodes.length = 0; - }; - f.prototype.pauseAllSounds = function() { - h.getAudioContext().suspend(); - }; - f.prototype.unpauseAllSounds = function() { - h.getAudioContext().resume(); - }; - f.prototype.onExecute = function() { - if (this.inputs) { - for (var a = 0; a < this.inputs.length; ++a) { - var b = this.inputs[a]; - if (null != b.link) { - var c = this.getInputData(a); - if (void 0 !== c) { - if ("gain" == b.name) { - this.audionode.gain.value = c; - } else { - if ("playbackRate" == b.name) { - for (this.properties.playbackRate = c, b = 0; b < this._audionodes.length; ++b) { - this._audionodes[b].playbackRate.value = c; - } - } - } - } - } - } - } - if (this.outputs) { - for (a = 0; a < this.outputs.length; ++a) { - "buffer" == this.outputs[a].name && this._audiobuffer && this.setOutputData(a, this._audiobuffer); - } - } - }; - f.prototype.onAction = function(a) { - this._audiobuffer && ("Play" == a ? this.playBuffer(this._audiobuffer) : "Stop" == a && this.stopAllSounds()); - }; - f.prototype.onPropertyChanged = function(a, b) { - if ("src" == a) { - this.loadSound(b); - } else { - if ("gain" == a) { - this.audionode.gain.value = b; - } else { - if ("playbackRate" == a) { - for (a = 0; a < this._audionodes.length; ++a) { - this._audionodes[a].playbackRate.value = b; - } - } - } - } - }; - f.prototype.playBuffer = function(a) { - var b = this, c = h.getAudioContext().createBufferSource(); - this._last_sourcenode = c; - c.graphnode = this; - c.buffer = a; - c.loop = this.properties.loop; - c.playbackRate.value = this.properties.playbackRate; - this._audionodes.push(c); - c.connect(this.audionode); - this._audionodes.push(c); - c.onended = function() { - b.trigger("ended"); - var a = b._audionodes.indexOf(c); - -1 != a && b._audionodes.splice(a, 1); - }; - c.started || (c.started = !0, c.start()); - return c; - }; - f.prototype.loadSound = function(a) { - var b = this; - this._request && (this._request.abort(), this._request = null); - this._audiobuffer = null; - this._loading_audio = !1; - a && (this._request = h.loadSound(a, function(a) { - this.boxcolor = y.NODE_DEFAULT_BOXCOLOR; - b._audiobuffer = a; - b._loading_audio = !1; - if (b.graph && b.graph.status === LGraph.STATUS_RUNNING) { - b.onStart(); - } - }), this._loading_audio = !0, this.boxcolor = "#AA4"); - }; - f.prototype.onConnectionsChange = h.onConnectionsChange; - f.prototype.onGetInputs = function() { - return [["playbackRate", "number"], ["Play", y.ACTION], ["Stop", y.ACTION]]; - }; - f.prototype.onGetOutputs = function() { - return [["buffer", "audiobuffer"], ["ended", y.EVENT]]; - }; - f.prototype.onDropFile = function(a) { - this._dropped_url && URL.revokeObjectURL(this._dropped_url); - a = URL.createObjectURL(a); - this.properties.src = a; - this.loadSound(a); - this._dropped_url = a; - }; - f.title = "Source"; - f.desc = "Plays audio"; - y.registerNodeType("audio/source", f); - k.prototype.onPropertyChanged = function(a, b) { - this.audionode[a] = b; - }; - k.prototype.onExecute = function() { - if (this.isOutputConnected(0)) { - var a = this.audionode.frequencyBinCount; - this._freq_bin && this._freq_bin.length == a || (this._freq_bin = new Uint8Array(a)); - this.audionode.getByteFrequencyData(this._freq_bin); - this.setOutputData(0, this._freq_bin); - } - this.isOutputConnected(1) && (a = this.audionode.frequencyBinCount, this._time_bin && this._time_bin.length == a || (this._time_bin = new Uint8Array(a)), this.audionode.getByteTimeDomainData(this._time_bin), this.setOutputData(1, this._time_bin)); - for (a = 1; a < this.inputs.length; ++a) { - var b = this.inputs[a]; - if (null != b.link) { - var c = this.getInputData(a); - void 0 !== c && (this.audionode[b.name].value = c); - } - } - }; - k.prototype.onGetInputs = function() { - return [["minDecibels", "number"], ["maxDecibels", "number"], ["smoothingTimeConstant", "number"]]; - }; - k.prototype.onGetOutputs = function() { - return [["freqs", "array"], ["samples", "array"]]; - }; - k.title = "Analyser"; - k.desc = "Audio Analyser"; - y.registerNodeType("audio/analyser", k); - n.prototype.onExecute = function() { - if (this.inputs && this.inputs.length) { - for (var a = 1; a < this.inputs.length; ++a) { - var b = this.inputs[a], c = this.getInputData(a); - void 0 !== c && (this.audionode[b.name].value = c); - } - } - }; - h.createAudioNodeWrapper(n); - n.title = "Gain"; - n.desc = "Audio gain"; - y.registerNodeType("audio/gain", n); - h.createAudioNodeWrapper(g); - g.prototype.onRemove = function() { - this._dropped_url && URL.revokeObjectURL(this._dropped_url); - }; - g.prototype.onPropertyChanged = function(a, b) { - "impulse_src" == a ? this.loadImpulse(b) : "normalize" == a && (this.audionode.normalize = b); - }; - g.prototype.onDropFile = function(a) { - this._dropped_url && URL.revokeObjectURL(this._dropped_url); - this._dropped_url = URL.createObjectURL(a); - this.properties.impulse_src = this._dropped_url; - this.loadImpulse(this._dropped_url); - }; - g.prototype.loadImpulse = function(a) { - var b = this; - this._request && (this._request.abort(), this._request = null); - this._impulse_buffer = null; - this._loading_impulse = !1; - a && (this._request = h.loadSound(a, function(a) { - b._impulse_buffer = a; - b.audionode.buffer = a; - console.log("Impulse signal set"); - b._loading_impulse = !1; - }), this._loading_impulse = !0); - }; - g.title = "Convolver"; - g.desc = "Convolves the signal (used for reverb)"; - y.registerNodeType("audio/convolver", g); - h.createAudioNodeWrapper(q); - q.prototype.onExecute = function() { - if (this.inputs && this.inputs.length) { - for (var a = 1; a < this.inputs.length; ++a) { - var b = this.inputs[a]; - if (null != b.link) { - var c = this.getInputData(a); - void 0 !== c && (this.audionode[b.name].value = c); - } - } - } - }; - q.prototype.onGetInputs = function() { - return [["threshold", "number"], ["knee", "number"], ["ratio", "number"], ["reduction", "number"], ["attack", "number"], ["release", "number"]]; - }; - q.title = "DynamicsCompressor"; - q.desc = "Dynamics Compressor"; - y.registerNodeType("audio/dynamicsCompressor", q); - r.prototype.onExecute = function() { - if (this.inputs && this.inputs.length) { - var a = this.getInputData(1); - void 0 !== a && (this.audionode.curve = a); - } - }; - r.prototype.setWaveShape = function(a) { - this.audionode.curve = a; - }; - h.createAudioNodeWrapper(r); - v.prototype.getAudioNodeInInputSlot = function(a) { - if (0 == a) { - return this.audionode1; - } - if (2 == a) { - return this.audionode2; - } - }; - v.prototype.onPropertyChanged = function(a, b) { - "gain1" == a ? this.audionode1.gain.value = b : "gain2" == a && (this.audionode2.gain.value = b); - }; - v.prototype.onExecute = function() { - if (this.inputs && this.inputs.length) { - for (var a = 1; a < this.inputs.length; ++a) { - var b = this.inputs[a]; - null != b.link && "audio" != b.type && (b = this.getInputData(a), void 0 !== b && (1 == a ? this.audionode1.gain.value = b : 3 == a && (this.audionode2.gain.value = b))); - } - } - }; - h.createAudioNodeWrapper(v); - v.title = "Mixer"; - v.desc = "Audio mixer"; - y.registerNodeType("audio/mixer", v); - h.createAudioNodeWrapper(w); - w.prototype.onExecute = function() { - var a = this.getInputData(1); - void 0 !== a && (this.audionode.delayTime.value = a); - }; - w.title = "Delay"; - w.desc = "Audio delay"; - y.registerNodeType("audio/delay", w); - l.prototype.onExecute = function() { - if (this.inputs && this.inputs.length) { - for (var a = 1; a < this.inputs.length; ++a) { - var b = this.inputs[a]; - if (null != b.link) { - var c = this.getInputData(a); - void 0 !== c && (this.audionode[b.name].value = c); - } - } - } - }; - l.prototype.onGetInputs = function() { - return [["frequency", "number"], ["detune", "number"], ["Q", "number"]]; - }; - h.createAudioNodeWrapper(l); - l.title = "BiquadFilter"; - l.desc = "Audio filter"; - y.registerNodeType("audio/biquadfilter", l); - d.prototype.onStart = function() { - this.audionode.started || (this.audionode.started = !0, this.audionode.start()); - }; - d.prototype.onStop = function() { - this.audionode.started && (this.audionode.started = !1, this.audionode.stop()); - }; - d.prototype.onPause = function() { - this.onStop(); - }; - d.prototype.onUnpause = function() { - this.onStart(); - }; - d.prototype.onExecute = function() { - if (this.inputs && this.inputs.length) { - for (var a = 0; a < this.inputs.length; ++a) { - var b = this.inputs[a]; - if (null != b.link) { - var c = this.getInputData(a); - void 0 !== c && (this.audionode[b.name].value = c); - } - } - } - }; - d.prototype.onGetInputs = function() { - return [["frequency", "number"], ["detune", "number"], ["type", "string"]]; - }; - h.createAudioNodeWrapper(d); - d.title = "Oscillator"; - d.desc = "Oscillator"; - y.registerNodeType("audio/oscillator", d); - c.prototype.onExecute = function() { - this._last_buffer = this.getInputData(0); - var a = this.getInputData(1); - void 0 !== a && (this.properties.mark = a); - this.setDirtyCanvas(!0, !1); - }; - c.prototype.onDrawForeground = function(a) { - if (this._last_buffer) { - var b = this._last_buffer, c = b.length / this.size[0], d = this.size[1]; - a.fillStyle = "black"; - a.fillRect(0, 0, this.size[0], this.size[1]); - a.strokeStyle = "white"; - a.beginPath(); - var e = 0; - if (this.properties.continuous) { - a.moveTo(e, d); - for (var g = 0; g < b.length; g += c) { - a.lineTo(e, d - b[g | 0] / 255 * d), e++; - } - } else { - for (g = 0; g < b.length; g += c) { - a.moveTo(e + 0.5, d), a.lineTo(e + 0.5, d - b[g | 0] / 255 * d), e++; - } - } - a.stroke(); - 0 <= this.properties.mark && (b = h.getAudioContext().sampleRate / b.length, e = this.properties.mark / b * 2 / c, e >= this.size[0] && (e = this.size[0] - 1), a.strokeStyle = "red", a.beginPath(), a.moveTo(e, d), a.lineTo(e, 0), a.stroke()); - } - }; - c.title = "Visualization"; - c.desc = "Audio Visualization"; - y.registerNodeType("audio/visualization", c); - a.prototype.onExecute = function() { - if (this._freqs = this.getInputData(0)) { - var a = this.properties.band, b = this.getInputData(1); - void 0 !== b && (a = b); - b = h.getAudioContext().sampleRate / this._freqs.length; - b = a / b * 2; - b >= this._freqs.length ? b = this._freqs[this._freqs.length - 1] : (a = b | 0, b -= a, b = this._freqs[a] * (1 - b) + this._freqs[a + 1] * b); - this.setOutputData(0, b / 255 * this.properties.amplitude); - } - }; - a.prototype.onGetInputs = function() { - return [["band", "number"]]; - }; - a.title = "Signal"; - a.desc = "extract the signal of some frequency"; - y.registerNodeType("audio/signal", a); - b.prototype.onAdded = function(a) { - a.status == LGraph.STATUS_RUNNING && (this.audionode.onaudioprocess = this._callback); - }; - b["@code"] = {widget:"code"}; - b.prototype.onStart = function() { - this.audionode.onaudioprocess = this._callback; - }; - b.prototype.onStop = function() { - this.audionode.onaudioprocess = b._bypass_function; - }; - b.prototype.onPause = function() { - this.audionode.onaudioprocess = b._bypass_function; - }; - b.prototype.onUnpause = function() { - this.audionode.onaudioprocess = this._callback; - }; - b.prototype.onExecute = function() { - }; - b.prototype.onRemoved = function() { - this.audionode.onaudioprocess = b._bypass_function; - }; - b.prototype.processCode = function() { - try { - this._script = new (new Function("properties", this.properties.code))(this.properties), this._old_code = this.properties.code, this._callback = this._script.onaudioprocess; - } catch (m) { - console.error("Error in onaudioprocess code", m), this._callback = b._bypass_function, this.audionode.onaudioprocess = this._callback; - } - }; - b.prototype.onPropertyChanged = function(a, b) { - "code" == a && (this.properties.code = b, this.processCode(), this.graph && this.graph.status == LGraph.STATUS_RUNNING && (this.audionode.onaudioprocess = this._callback)); - }; - b.default_function = function() { - this.onaudioprocess = function(a) { - var b = a.inputBuffer; - a = a.outputBuffer; - for (var c = 0; c < a.numberOfChannels; c++) { - for (var d = b.getChannelData(c), e = a.getChannelData(c), g = 0; g < b.length; g++) { - e[g] = d[g]; - } - } - }; - }; - h.createAudioNodeWrapper(b); - b.title = "Script"; - b.desc = "apply script to signal"; - y.registerNodeType("audio/script", b); - e.title = "Destination"; - e.desc = "Audio output"; - y.registerNodeType("audio/destination", e); -})(this); -(function(u) { - function f() { - this.size = [60, 20]; - this.addInput("send", n.ACTION); - this.addOutput("received", n.EVENT); - this.addInput("in", 0); - this.addOutput("out", 0); - this.properties = {url:"", room:"lgraph"}; - this._ws = null; - this._last_data = []; - } - function k() { - this.size = [60, 20]; - this.addInput("send", n.ACTION); - this.addOutput("received", n.EVENT); - this.addInput("in", 0); - this.addOutput("out", 0); - this.properties = {url:"tamats.com:55000", room:"lgraph", save_bandwidth:!0}; - this._server = null; - this.createSocket(); - this._last_input_data = []; - this._last_output_data = []; - } - var n = u.LiteGraph; - f.title = "WebSocket"; - f.desc = "Send data through a websocket"; - f.prototype.onPropertyChanged = function(g, f) { - "url" == g && this.createSocket(); - }; - f.prototype.onExecute = function() { - !this._ws && this.properties.url && this.createSocket(); - if (this._ws && this._ws.readyState == WebSocket.OPEN) { - for (var g = this.properties.room, f = 1; f < this.inputs.length; ++f) { - var k = this.getInputData(f); - if (null != k) { - try { - var n = JSON.stringify({type:0, room:g, channel:f, data:k}); - } catch (w) { - continue; - } - this._ws.send(n); - } - } - for (f = 1; f < this.outputs.length; ++f) { - this.setOutputData(f, this._last_data[f]); - } - } - }; - f.prototype.createSocket = function() { - var g = this, f = this.properties.url; - "ws" != f.substr(0, 2) && (f = "ws://" + f); - this._ws = new WebSocket(f); - this._ws.onopen = function() { - console.log("ready"); - g.boxcolor = "#8E8"; - }; - this._ws.onmessage = function(f) { - var k = JSON.parse(f.data); - k.room && k.room != this.properties.room || (1 == f.data.type ? g.triggerSlot(0, k) : g._last_data[f.data.channel || 0] = k.data); - }; - this._ws.onerror = function(f) { - console.log("couldnt connect to websocket"); - g.boxcolor = "#E88"; - }; - this._ws.onclose = function(f) { - console.log("connection closed"); - g.boxcolor = "#000"; - }; - }; - f.prototype.send = function(g) { - this._ws && this._ws.readyState == WebSocket.OPEN && this._ws.send(JSON.stringify({type:1, msg:g})); - }; - f.prototype.onAction = function(g, f) { - this._ws && this._ws.readyState == WebSocket.OPEN && this._ws.send({type:1, room:this.properties.room, action:g, data:f}); - }; - f.prototype.onGetInputs = function() { - return [["in", 0]]; - }; - f.prototype.onGetOutputs = function() { - return [["out", 0]]; - }; - n.registerNodeType("network/websocket", f); - k.title = "SillyClient"; - k.desc = "Connects to SillyServer to broadcast messages"; - k.prototype.onPropertyChanged = function(g, f) { - g = this.properties.url + "/" + this.properties.room; - this._server && this._final_url != g && (this._server.connect(this.properties.url, this.properties.room), this._final_url = g); - }; - k.prototype.onExecute = function() { - if (this._server && this._server.is_connected) { - for (var g = this.properties.save_bandwidth, f = 1; f < this.inputs.length; ++f) { - var k = this.getInputData(f); - null == k || g && this._last_input_data[f] == k || (this._server.sendMessage({type:0, channel:f, data:k}), this._last_input_data[f] = k); - } - for (f = 1; f < this.outputs.length; ++f) { - this.setOutputData(f, this._last_output_data[f]); - } - } - }; - k.prototype.createSocket = function() { - var f = this; - "undefined" == typeof SillyClient ? (this._error || console.error("SillyClient node cannot be used, you must include SillyServer.js"), this._error = !0) : (this._server = new SillyClient, this._server.on_ready = function() { - console.log("ready"); - f.boxcolor = "#8E8"; - }, this._server.on_message = function(g, k) { - g = null; - try { - g = JSON.parse(k); - } catch (v) { - return; - } - 1 == g.type ? f.triggerSlot(0, g) : f._last_output_data[g.channel || 0] = g.data; - }, this._server.on_error = function(g) { - console.log("couldnt connect to websocket"); - f.boxcolor = "#E88"; - }, this._server.on_close = function(g) { - console.log("connection closed"); - f.boxcolor = "#000"; - }, this.properties.url && this.properties.room && (this._server.connect(this.properties.url, this.properties.room), this._final_url = this.properties.url + "/" + this.properties.room)); - }; - k.prototype.send = function(f) { - this._server && this._server.is_connected && this._server.sendMessage({type:1, data:f}); - }; - k.prototype.onAction = function(f, k) { - this._server && this._server.is_connected && this._server.sendMessage({type:1, action:f, data:k}); - }; - k.prototype.onGetInputs = function() { - return [["in", 0]]; - }; - k.prototype.onGetOutputs = function() { - return [["out", 0]]; - }; - n.registerNodeType("network/sillyclient", k); -})(this); - +(function(t){function g(a){k.debug&&console.log("Graph created");this.list_of_graphcanvas=null;this.clear();a&&this.configure(a)}function d(a){this._ctor(a)}function p(a){this._ctor(a)}function e(a,b,h){h=h||{};this.background_image="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQBJREFUeNrs1rEKwjAUhlETUkj3vP9rdmr1Ysammk2w5wdxuLgcMHyptfawuZX4pJSWZTnfnu/lnIe/jNNxHHGNn//HNbbv+4dr6V+11uF527arU7+u63qfa/bnmh8sWLBgwYJlqRf8MEptXPBXJXa37BSl3ixYsGDBMliwFLyCV/DeLIMFCxYsWLBMwSt4Be/NggXLYMGCBUvBK3iNruC9WbBgwYJlsGApeAWv4L1ZBgsWLFiwYJmCV/AK3psFC5bBggULloJX8BpdwXuzYMGCBctgwVLwCl7Be7MMFixYsGDBsu8FH1FaSmExVfAxBa/gvVmwYMGCZbBg/W4vAQYA5tRF9QYlv/QAAAAASUVORK5CYII="; +a&&a.constructor===String&&(a=document.querySelector(a));this.max_zoom=10;this.min_zoom=0.1;this.zoom_modify_alpha=!0;this.title_text_font="bold "+k.NODE_TEXT_SIZE+"px Arial";this.inner_text_font="normal "+k.NODE_SUBTEXT_SIZE+"px Arial";this.node_title_color=k.NODE_TITLE_COLOR;this.default_link_color=k.LINK_COLOR;this.default_connection_color={input_off:"#AAB",input_on:"#7F7",output_off:"#AAB",output_on:"#7F7"};this.highquality_render=!0;this.use_gradients=!1;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_searchbox=this.allow_interaction=this.allow_dragnodes=this.allow_dragcanvas=this.show_info=!0;this.drag_mode=!1;this.filter=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.canvas_mouse=[0,0];this.onSearchBoxSelection=this.onSearchBox= +null;this.connections_width=3;this.round_radius=8;this.node_widget=this.current_node=null;this.last_mouse_position=[0,0];b&&b.attachCanvas(this);this.setCanvas(a);this.clear();h.skip_render||this.startRendering();this.autoresize=h.autoresize}function r(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,h,c,f,l){return ha&&cb?!0:!1}function s(a,b){var h=a[0]+a[2],c=a[1]+a[3],f=b[1]+b[3];return a[0]>b[0]+b[2]||a[1]>f||hk.width-e.width-10&&(l=k.width-e.width-10);n>k.height-e.height-10&&(n=k.height-e.height-10)}f.style.left=l+"px";f.style.top=n+"px"}var k=t.LiteGraph={CANVAS_GRID_SIZE:10,NODE_TITLE_HEIGHT:20,NODE_SLOT_HEIGHT:15,NODE_WIDGET_HEIGHT:20,NODE_WIDTH:140,NODE_MIN_WIDTH:50, +NODE_COLLAPSED_RADIUS:10,NODE_COLLAPSED_WIDTH:80,NODE_TITLE_COLOR:"#999",NODE_TEXT_SIZE:14,NODE_TEXT_COLOR:"#AAA",NODE_SUBTEXT_SIZE:12,NODE_DEFAULT_COLOR:"#333",NODE_DEFAULT_BGCOLOR:"#444",NODE_DEFAULT_BOXCOLOR:"#888",NODE_DEFAULT_SHAPE:"box",LINK_COLOR:"#AAD",EVENT_LINK_COLOR:"#F85",CONNECTING_LINK_COLOR:"#AFA",MAX_NUMBER_OF_NODES:1E3,DEFAULT_POSITION:[100,100],VALID_SHAPES:["default","box","round","card"],BOX_SHAPE:1,ROUND_SHAPE:2,CIRCLE_SHAPE:3,CARD_SHAPE:4,ARROW_SHAPE:5,INPUT:1,OUTPUT:2,EVENT:-1, +ACTION:-1,ALWAYS:0,ON_EVENT:1,NEVER:2,ON_TRIGGER:3,UP:1,DOWN:2,LEFT:3,RIGHT:4,CENTER:5,NORMAL_TITLE:0,NO_TITLE:1,TRANSPARENT_TITLE:2,AUTOHIDE_TITLE:3,proxy:null,node_images_path:"",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;k.debug&&console.log("Node registered: "+a);a.split("/");var h=b.name,c=a.lastIndexOf("/"); +b.category=a.substr(0,c);b.title||(b.title=h);if(b.prototype)for(var f in d.prototype)b.prototype[f]||(b.prototype[f]=d.prototype[f]);Object.defineProperty(b.prototype,"shape",{set:function(a){switch(a){case "default":delete this._shape;break;case "box":this._shape=k.BOX_SHAPE;break;case "round":this._shape=k.ROUND_SHAPE;break;case "circle":this._shape=k.CIRCLE_SHAPE;break;case "card":this._shape=k.CARD_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[h]=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(f in b.supported_extensions)this.node_types_by_file_extension[b.supported_extensions[f].toLowerCase()]=b},wrapFunctionAsNode:function(a,b,h,c){for(var f=Array(b.length),l="",n=k.getParameterNames(b),e=0;en&&(n=f.size[0]),e+=f.size[1]+a;b+=n+a}this.setDirtyCanvas(!0,!0)};g.prototype.getTime=function(){return this.globaltime};g.prototype.getFixedTime=function(){return this.fixedtime};g.prototype.getElapsedTime=function(){return this.elapsed_time};g.prototype.sendEventToAllNodes=function(a,b,h){h=h||k.ALWAYS;var c=this._nodes_in_order?this._nodes_in_order:this._nodes;if(c)for(var f=0,l=c.length;f=k.MAX_NUMBER_OF_NODES)throw"LiteGraph: max number of nodes in a graph reached";null==a.id||-1==a.id?a.id=++this.last_node_id:this.last_node_ida.length||(this._pos[0]=a[0],this._pos[1]=a[1])},get:function(){return this._pos},enumerable:!0});this.id=-1;this.type=null;this.inputs= +[];this.outputs=[];this.connections=[];this.properties={};this.properties_info=[];this.data=null;this.flags={}};d.prototype.configure=function(a){this.graph&&this.graph._version++;for(var b in a)if("console"!=b)if("properties"==b)for(var h in a.properties){if(this.properties[h]=a.properties[h],this.onPropertyChanged)this.onPropertyChanged(h,a.properties[h])}else null!=a[b]&&("object"==typeof a[b]?this[b]&&this[b].configure?this[b].configure(a[b]):this[b]=k.cloneObject(a[b],this[b]):this[b]=a[b]); +a.title||(this.title=this.constructor.title);if(this.onConnectionsChange){if(this.inputs)for(var c=0;c=this.outputs.length)){var h= +this.outputs[a];if(h&&(h._data=b,this.outputs[a].links))for(h=0;h=this.inputs.length||null==this.inputs[a].link)){var h=this.graph.links[this.inputs[a].link];if(!h)return null;if(!b)return h.data;var c=this.graph.getNodeById(h.origin_id);if(!c)return h.data;if(c.updateOutputData)c.updateOutputData(h.origin_slot);else if(c.onExecute)c.onExecute();return h.data}}; +d.prototype.getInputDataByName=function(a,b){var h=this.findInputSlot(a);return-1==h?null:this.getInputData(h,b)};d.prototype.isInputConnected=function(a){return this.inputs?a=this.inputs.length)return null;a=this.inputs[a];return a&&null!==a.link?(a=this.graph.links[a.link])?this.graph.getNodeById(a.origin_id): +null:null};d.prototype.getInputOrProperty=function(a){if(!this.inputs||!this.inputs.length)return this.properties?this.properties[a]:null;for(var b=0,h=this.inputs.length;b=this.outputs.length?null:this.outputs[a]._data};d.prototype.getOutputInfo=function(a){return this.outputs?a=this.outputs.length)return null;a=this.outputs[a];if(!a.links||0==a.links.length)return null; +for(var b=[],h=0;ha&&this.pos[1]-c-hb)return!0;return!1};d.prototype.getSlotInPosition=function(a,b){if(this.inputs)for(var h=0,c=this.inputs.length;h=this.outputs.length)return k.debug&&console.log("Connect: Error, slot number not found"),!1;b&&b.constructor===Number&&(b=this.graph.getNodeById(b));if(!b)throw"target node is null";if(b==this)return!1;if(h.constructor===String){if(h=b.findInputSlot(h),-1==h)return k.debug&&console.log("Connect: Error, no slot of name "+h),!1}else{if(h===k.EVENT)return!1;if(!b.inputs||h>=b.inputs.length)return k.debug&&console.log("Connect: Error, slot number not found"), +!1}null!=b.inputs[h].link&&b.disconnectInput(h);var c=this.outputs[a];if(b.onConnectInput&&!1===b.onConnectInput(h,c.type,c))return!1;var f=b.inputs[h];if(k.isValidConnection(c.type,f.type)){var l={id:this.graph.last_link_id++,type:f.type,origin_id:this.id,origin_slot:a,target_id:b.id,target_slot:h};this.graph.links[l.id]=l;null==c.links&&(c.links=[]);c.links.push(l.id);b.inputs[h].link=l.id;this.graph&&this.graph._version++;if(this.onConnectionsChange)this.onConnectionsChange(k.OUTPUT,a,!0,l,c); +if(b.onConnectionsChange)b.onConnectionsChange(k.INPUT,h,!0,l,f);if(this.graph&&this.graph.onNodeConnectionChange)this.graph.onNodeConnectionChange(k.OUTPUT,this,a,b,h)}this.setDirtyCanvas(!1,!0);this.graph.connectionChange(this);return!0};d.prototype.disconnectOutput=function(a,b){if(a.constructor===String){if(a=this.findOutputSlot(a),-1==a)return k.debug&&console.log("Connect: Error, no slot of name "+a),!1}else if(!this.outputs||a>=this.outputs.length)return k.debug&&console.log("Connect: Error, slot number not found"), +!1;var h=this.outputs[a];if(!h.links||0==h.links.length)return!1;if(b){b.constructor===Number&&(b=this.graph.getNodeById(b));if(!b)throw"Target Node not found";for(var c=0,f=h.links.length;c=this.inputs.length)return k.debug&&console.log("Connect: Error, slot number not found"),!1;var b=this.inputs[a];if(!b)return!1;var h=this.inputs[a].link;this.inputs[a].link=null;var c=this.graph.links[h];if(c){var f=this.graph.getNodeById(c.origin_id);if(!f)return!1;var l=f.outputs[c.origin_slot];if(!l||!l.links||0==l.links.length)return!1;for(var n=0,e=l.links.length;nb&&this.inputs[b].pos? +[this.pos[0]+this.inputs[b].pos[0],this.pos[1]+this.inputs[b].pos[1]]:!a&&this.outputs.length>b&&this.outputs[b].pos?[this.pos[0]+this.outputs[b].pos[0],this.pos[1]+this.outputs[b].pos[1]]:this.flags.horizontal?a?[this.pos[0]+this.size[0]/this.inputs.length*(b+0.5),this.pos[1]-k.NODE_TITLE_HEIGHT]:[this.pos[0]+this.size[0]/this.outputs.length*(b+0.5),this.pos[1]+this.size[1]]:a?[this.pos[0],this.pos[1]+10+b*k.NODE_SLOT_HEIGHT+(this.constructor.slot_start_y||0)]:[this.pos[0]+this.size[0]+1,this.pos[1]+ +10+b*k.NODE_SLOT_HEIGHT+(this.constructor.slot_start_y||0)]};d.prototype.alignToGrid=function(){this.pos[0]=k.CANVAS_GRID_SIZE*Math.round(this.pos[0]/k.CANVAS_GRID_SIZE);this.pos[1]=k.CANVAS_GRID_SIZE*Math.round(this.pos[1]/k.CANVAS_GRID_SIZE)};d.prototype.trace=function(a){this.console||(this.console=[]);this.console.push(a);this.console.length>d.MAX_CONSOLE&&this.console.shift();this.graph.onNodeTrace(this,a)};d.prototype.setDirtyCanvas=function(a,b){this.graph&&this.graph.sendActionToCanvas("setDirty", +[a,b])};d.prototype.loadImage=function(a){var b=new Image;b.src=k.node_images_path+a;b.ready=!1;var h=this;b.onload=function(){this.ready=!0;h.setDirtyCanvas(!0)};return b};d.prototype.captureInput=function(a){if(this.graph&&this.graph.list_of_graphcanvas)for(var b=this.graph.list_of_graphcanvas,h=0;ha.length||(this._pos[0]=a[0],this._pos[1]=a[1])},get:function(){return this._pos},enumerable:!0});Object.defineProperty(this,"size",{set:function(a){!a||2>a.length||(this._size[0]=Math.max(140,a[0]),this._size[1]=Math.max(80,a[1]))},get:function(){return this._size},enumerable:!0})};p.prototype.configure=function(a){this.title=a.title;this._bounding.set(a.bounding); +this.color=a.color};p.prototype.serialize=function(){var a=this._bounding;return{title:this.title,bounding:[a[0],a[1],a[2],a[3]],color:this.color}};p.prototype.move=function(a,b,h){this._pos[0]+=a;this._pos[1]+=b;if(!h)for(h=0;h element, you passed a "+a.localName;throw"This browser doesnt support Canvas";}null==(this.ctx=a.getContext("2d"))&&(a.webgl_enabled|| +console.warn("This canvas seems to be WebGL, enabling WebGL renderer"),this.enableWebGL());this._mousemove_callback=this.processMouseMove.bind(this);this._mouseup_callback=this.processMouseUp.bind(this);b||this.bindEvents()}};e.prototype._doNothing=function(a){a.preventDefault();return!1};e.prototype._doReturnTrue=function(a){a.preventDefault();return!0};e.prototype.bindEvents=function(){if(this._events_binded)console.warn("LGraphCanvas: events already binded");else{var a=this.canvas,b=this.getCanvasWindow().document; +this._mousedown_callback=this.processMouseDown.bind(this);this._mousewheel_callback=this.processMouseWheel.bind(this);a.addEventListener("mousedown",this._mousedown_callback,!0);a.addEventListener("mousemove",this._mousemove_callback);a.addEventListener("mousewheel",this._mousewheel_callback,!1);a.addEventListener("contextmenu",this._doNothing);a.addEventListener("DOMMouseScroll",this._mousewheel_callback,!1);a.addEventListener("touchstart",this.touchHandler,!0);a.addEventListener("touchmove",this.touchHandler, +!0);a.addEventListener("touchend",this.touchHandler,!0);a.addEventListener("touchcancel",this.touchHandler,!0);this._key_callback=this.processKey.bind(this);a.addEventListener("keydown",this._key_callback,!0);b.addEventListener("keyup",this._key_callback,!0);this._ondrop_callback=this.processDrop.bind(this);a.addEventListener("dragover",this._doNothing,!1);a.addEventListener("dragend",this._doNothing,!1);a.addEventListener("drop",this._ondrop_callback,!1);a.addEventListener("dragenter",this._doReturnTrue, +!1);this._events_binded=!0}};e.prototype.unbindEvents=function(){if(this._events_binded){var a=this.getCanvasWindow().document;this.canvas.removeEventListener("mousedown",this._mousedown_callback);this.canvas.removeEventListener("mousewheel",this._mousewheel_callback);this.canvas.removeEventListener("DOMMouseScroll",this._mousewheel_callback);this.canvas.removeEventListener("keydown",this._key_callback);a.removeEventListener("keyup",this._key_callback);this.canvas.removeEventListener("contextmenu", +this._doNothing);this.canvas.removeEventListener("drop",this._ondrop_callback);this.canvas.removeEventListener("dragenter",this._doReturnTrue);this.canvas.removeEventListener("touchstart",this.touchHandler);this.canvas.removeEventListener("touchmove",this.touchHandler);this.canvas.removeEventListener("touchend",this.touchHandler);this.canvas.removeEventListener("touchcancel",this.touchHandler);this._ondrop_callback=this._key_callback=this._mousewheel_callback=this._mousedown_callback=null;this._events_binded= +!1}else console.warn("LGraphCanvas: no events binded")};e.getFileExtension=function(a){var b=a.indexOf("?");-1!=b&&(a=a.substr(0,b));b=a.lastIndexOf(".");return-1==b?"":a.substr(b+1).toLowerCase()};e.prototype.enableWebGL=function(){if(void 0===typeof GL)throw"litegl.js must be included to use a WebGL canvas";if(void 0===typeof enableWebGLCanvas)throw"webglCanvas.js must be included to use this feature";this.gl=this.ctx=enableWebGLCanvas(this.canvas);this.ctx.webgl=!0;this.bgcanvas=this.canvas;this.bgctx= +this.gl;this.canvas.webgl_enabled=!0};e.prototype.setDirty=function(a,b){a&&(this.dirty_canvas=!0);b&&(this.dirty_bgcanvas=!0)};e.prototype.getCanvasWindow=function(){if(!this.canvas)return window;var a=this.canvas.ownerDocument;return a.defaultView||a.parentWindow};e.prototype.startRendering=function(){function a(){this.pause_rendering||this.draw();var b=this.getCanvasWindow();this.is_rendering&&b.requestAnimationFrame(a.bind(this))}this.is_rendering||(this.is_rendering=!0,a.call(this))};e.prototype.stopRendering= +function(){this.is_rendering=!1};e.prototype.processMouseDown=function(a){if(this.graph){this.adjustMouseEvent(a);var b=this.getCanvasWindow();e.active_canvas=this;this.canvas.removeEventListener("mousemove",this._mousemove_callback);b.document.addEventListener("mousemove",this._mousemove_callback,!0);b.document.addEventListener("mouseup",this._mouseup_callback,!0);var h=this.graph.getNodeOnPos(a.canvasX,a.canvasY,this.visible_nodes),c=!1,f=300>k.getTime()-this.last_mouseclick;this.canvas_mouse[0]= +a.canvasX;this.canvas_mouse[1]=a.canvasY;k.closeAllContextMenus(b);if(1==a.which){a.ctrlKey&&(this.dragging_rectangle=new Float32Array(4),this.dragging_rectangle[0]=a.canvasX,this.dragging_rectangle[1]=a.canvasY,this.dragging_rectangle[2]=1,this.dragging_rectangle[3]=1,c=!0);var l=!1;if(h&&this.allow_interaction&&!c){this.live_mode||h.flags.pinned||this.bringToFront(h);if(!this.connecting_node&&!h.flags.collapsed&&!this.live_mode){if(h.outputs)for(var n=0,m=h.outputs.length;nr([a.canvasX,a.canvasY],[this.selected_group.pos[0]+this.selected_group.size[0], +this.selected_group.pos[1]+this.selected_group.size[1]])*this.scale?this.selected_group_resizing=!0:this.selected_group.recomputeInsideNodes()),f&&this.showSearchBox(a),l=!0;!c&&l&&this.allow_dragcanvas&&(this.dragging_canvas=!0)}else 2!=a.which&&3==a.which&&this.processContextMenu(h,a);this.last_mouse[0]=a.localX;this.last_mouse[1]=a.localY;this.last_mouseclick=k.getTime();this.graph.change();(!b.document.activeElement||"input"!=b.document.activeElement.nodeName.toLowerCase()&&"textarea"!=b.document.activeElement.nodeName.toLowerCase())&& +a.preventDefault();a.stopPropagation();if(this.onMouseDown)this.onMouseDown(a);return!1}};e.prototype.processMouseMove=function(a){this.autoresize&&this.resize();if(this.graph){e.active_canvas=this;this.adjustMouseEvent(a);var b=[a.localX,a.localY],h=[b[0]-this.last_mouse[0],b[1]-this.last_mouse[1]];this.last_mouse=b;this.canvas_mouse[0]=a.canvasX;this.canvas_mouse[1]=a.canvasY;this.node_widget&&(this.processNodeWidgets(this.node_widget[0],this.canvas_mouse,a,this.node_widget[1]),this.dirty_canvas= +!0);if(this.dragging_rectangle)this.dragging_rectangle[2]=a.canvasX-this.dragging_rectangle[0],this.dragging_rectangle[3]=a.canvasY-this.dragging_rectangle[1],this.dirty_canvas=!0;else if(this.selected_group)this.selected_group_resizing?this.selected_group.size=[a.canvasX-this.selected_group.pos[0],a.canvasY-this.selected_group.pos[1]]:(this.selected_group.move(h[0]/this.scale,h[1]/this.scale,a.ctrlKey),this.selected_group._nodes.length&&(this.dirty_canvas=!0)),this.dirty_bgcanvas=!0;else if(this.dragging_canvas)this.offset[0]+= +h[0]/this.scale,this.offset[1]+=h[1]/this.scale,this.dirty_bgcanvas=this.dirty_canvas=!0;else if(this.allow_interaction){this.connecting_node&&(this.dirty_canvas=!0);for(var c=this.graph.getNodeOnPos(a.canvasX,a.canvasY,this.visible_nodes),b=0,f=this.graph._nodes.length;bthis.dragging_rectangle[2]&&(this.dragging_rectangle[0]+=this.dragging_rectangle[2]);0>this.dragging_rectangle[3]&&(this.dragging_rectangle[1]+=this.dragging_rectangle[3]);this.dragging_rectangle[2]=Math.abs(this.dragging_rectangle[2]*this.scale);this.dragging_rectangle[3]=Math.abs(this.dragging_rectangle[3]*this.scale);for(var f=0;fa.click_time&&this.deselectAllNodes();this.dirty_canvas=!0;this.dragging_canvas=!1;if(this.node_over&&this.node_over.onMouseUp)this.node_over.onMouseUp(a,[a.canvasX-this.node_over.pos[0],a.canvasY-this.node_over.pos[1]]);if(this.node_capturing_input&&this.node_capturing_input.onMouseUp)this.node_capturing_input.onMouseUp(a, +[a.canvasX-this.node_capturing_input.pos[0],a.canvasY-this.node_capturing_input.pos[1]])}else 2==a.which?(this.dirty_canvas=!0,this.dragging_canvas=!1):3==a.which&&(this.dirty_canvas=!0,this.dragging_canvas=!1);this.graph.change();a.stopPropagation();a.preventDefault();return!1}};e.prototype.processMouseWheel=function(a){if(this.graph&&this.allow_dragcanvas){var b=null!=a.wheelDeltaY?a.wheelDeltaY:-60*a.detail;this.adjustMouseEvent(a);var h=this.scale;0b&&(h*=1/1.1);this.setZoom(h,[a.localX, +a.localY]);this.graph.change();a.preventDefault();return!1}};e.prototype.isOverNodeBox=function(a,b,h){var c=k.NODE_TITLE_HEIGHT;return q(b,h,a.pos[0]+2,a.pos[1]+2-c,c-4,c-4)?!0:!1};e.prototype.isOverNodeInput=function(a,b,h,c){if(a.inputs)for(var f=0,l=a.inputs.length;fthis.max_zoom?this.scale=this.max_zoom:this.scalec-this.graph._last_trigger_time)&&this.drawBackCanvas();(this.dirty_canvas||a)&&this.drawFrontCanvas();this.fps=this.render_time?1/this.render_time:0;this.frame+=1}};e.prototype.drawFrontCanvas=function(){this.dirty_canvas=!1;this.ctx||(this.ctx=this.bgcanvas.getContext("2d"));var a=this.ctx;if(a){a.start2D&& +a.start2D();var b=this.canvas;a.restore();a.setTransform(1,0,0,1,0,0);this.dirty_area&&(a.save(),a.beginPath(),a.rect(this.dirty_area[0],this.dirty_area[1],this.dirty_area[2],this.dirty_area[3]),a.clip());this.clear_background&&a.clearRect(0,0,b.width,b.height);this.bgcanvas==this.canvas?this.drawBackCanvas():a.drawImage(this.bgcanvas,0,0);if(this.onRender)this.onRender(b,a);this.show_info&&this.renderInfo(a);if(this.graph){a.save();a.scale(this.scale,this.scale);a.translate(this.offset[0],this.offset[1]); +for(var b=this.computeVisibleNodes(null,this.visible_nodes),c=0;cthis.scale?(b.beginPath(),b.rect(0,n,p,q),b.fill()):d==k.ROUND_SHAPE||d==k.CARD_SHAPE?(b.beginPath(),b.roundRect(0,n,p,q,this.round_radius,d==k.CARD_SHAPE?0:this.round_radius),b.fill()):d==k.CIRCLE_SHAPE&&(b.beginPath(),b.arc(0.5*c[0],0.5*c[1],0.5*c[0],0,2*Math.PI),b.fill()));b.shadowColor="transparent";a.bgImage&&a.bgImage.width&&b.drawImage(a.bgImage,0.5*(c[0]-a.bgImage.width),0.5*(c[1]- +a.bgImage.height));a.bgImageUrl&&!a.bgImage&&(a.bgImage=a.loadImage(a.bgImageUrl));a.onDrawBackground&&(a.gui_rects&&(a.gui_rects.length=0),a.onDrawBackground(b,this));if(x||g==k.TRANSPARENT_TITLE){if(g!=k.TRANSPARENT_TITLE){if(this.use_gradients){var r=e.gradients[m];r||(r=e.gradients[m]=b.createLinearGradient(0,0,400,0),r.addColorStop(0,m),r.addColorStop(1,"#000"));b.fillStyle=r}else b.fillStyle=m;r=b.globalAlpha;b.beginPath();if(d==k.BOX_SHAPE||0.5>this.scale)b.rect(0,-f,c[0]+1,f),b.fill();else if(d== +k.ROUND_SHAPE||d==k.CARD_SHAPE)b.roundRect(0,-f,c[0]+1,f,this.round_radius,a.flags.collapsed?this.round_radius:0),b.fill()}b.fillStyle=a.boxcolor||k.NODE_DEFAULT_BOXCOLOR;b.beginPath();d==k.ROUND_SHAPE||d==k.CIRCLE_SHAPE||d==k.CARD_SHAPE?b.arc(0.5*f,-0.5*f,0.5*(f-8),0,2*Math.PI):b.rect(4,-f+4,f-8,f-8);b.fill();b.globalAlpha=r;0.5b-n._last_time){var q=2-0.002*(b-n._last_time),r="rgba(255,255,255, "+q.toFixed(2)+")";this.renderLink(a,g,p,n,!0,q,r,m,d)}}}}}a.globalAlpha=1};e.prototype.renderLink=function(a,b,c,m,f,l,n,d,g){if(this.highquality_render){d=d||k.RIGHT;g=g||k.LEFT;var x= +r(b,c);this.render_connections_border&&0.6b[1]?0:Math.PI,a.save(),a.translate(f[0],f[1]),a.rotate(x),a.beginPath(),a.moveTo(-5,-5),a.lineTo(0,5),a.lineTo(5,-5),a.fill(),a.restore());if(l)for(l=0;5>l;++l)f=(0.001*k.getTime()+0.2*l)%1,f=this.computeConnectionPoint(b,c,f,d,g),a.beginPath(),a.arc(f[0],f[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,e,f){e=e||k.RIGHT;f=f||k.LEFT;var l=r(a,b),n=[a[0],a[1]],m=[b[0],b[1]];switch(e){case k.LEFT:n[0]+=-0.25*l;break;case k.RIGHT:n[0]+=0.25*l;break;case k.UP:n[1]+=-0.25*l;break;case k.DOWN:n[1]+=0.25*l}switch(f){case k.LEFT:m[0]+=-0.25*l;break;case k.RIGHT:m[0]+=0.25*l;break;case k.UP:m[1]+=-0.25*l;break;case k.DOWN:m[1]+=0.25*l}e=(1-c)*(1-c)*(1-c);f=3*(1-c)*(1-c)*c;l=3*(1-c)*c*c;c*=c*c;return[e*a[0]+f*n[0]+l*m[0]+c*b[0],e*a[1]+f*n[1]+l*m[1]+c*b[1]]};e.prototype.drawNodeWidgets=function(a, +b,c,e){if(!a.widgets||!a.widgets.length)return 0;var f=a.size[0];a=a.widgets;b+=2;for(var l=k.NODE_WIDGET_HEIGHT,n=0.5g.last_y&&lg.options.max&&(g.value=g.options.max)):"mousedown"==c.type&&(c=40>f?-1:f>n-40?1:0,"number"==g.type?(g.value+=0.1*c*(g.options.step||1),null!=g.options.min&&g.valueg.options.max&&(g.value=g.options.max)):c&&(c=g.options.values.indexOf(g.value)+c,c>=g.options.values.length&&(c=0),0>c&& +(c=g.options.values.length-1),g.value=g.options.values[c])),g.callback&&setTimeout(function(){g.callback(g.value,d,a,b)},20),this.dirty_canvas=!0}return g}}return null};e.prototype.drawGroups=function(a,b){if(this.graph){var c=this.graph._groups;b.save();b.globalAlpha=0.5;b.font="24px Arial";for(var e=0;ec&&0.01>b.editor_alpha&&(clearInterval(e),1>c&&(b.live_mode=!0));1"+q+""+ +a+"",value:q});if(g.length)return new k.ContextMenu(g,{event:c,callback:l,parentMenu:d,allow_html:!0,node:f},b),!1}};e.decodeHTML=function(a){var b=document.createElement("div");b.innerText=a;return b.innerHTML};e.onResizeNode=function(a,b,c,e,f){f&&(f.size=f.computeSize(),f.setDirtyCanvas(!0,!0))};e.onShowTitleEditor=function(a,b,c,d,f){function l(){f.title=g.value;m.parentNode.removeChild(m);f.setDirtyCanvas(!0,!0)}var m=document.createElement("div");m.className="graphdialog";m.innerHTML= +"Title";var g=m.querySelector("input");g&&(g.value=f.title,g.addEventListener("keydown",function(a){13==a.keyCode&&(l(),a.preventDefault(),a.stopPropagation())}));a=e.active_canvas.canvas;b=a.getBoundingClientRect();d=c=-20;b&&(c-=b.left,d-=b.top);event?(m.style.left=event.pageX+c+"px",m.style.top=event.pageY+d+"px"):(m.style.left=0.5*a.width+c+"px",m.style.top=0.5*a.height+d+"px");m.querySelector("button").addEventListener("click", +l);a.parentNode.appendChild(m)};e.prototype.showSearchBox=function(a){function b(b){if(b)if(f.onSearchBoxSelection)f.onSearchBoxSelection(b,a,s);else if(b=k.createNode(b))b.pos=s.convertEventToCanvas(a),s.graph.add(b);l.close()}function c(a){var b=p;p&&p.classList.remove("selected");p?(p=a?p.nextSibling:p.previousSibling)||(p=b):p=a?m.childNodes[0]:m.childNodes[m.childNodes.length];p&&(p.classList.add("selected"),p.scrollIntoView())}function d(){q=null;var a=r.value;g=null;m.innerHTML="";if(a)if(f.onSearchBox)f.onSearchBox(h, +a,s);else for(var c in k.registered_node_types)if(-1!=c.indexOf(a)){var h=document.createElement("div");g||(g=c);h.innerText=c;h.className="help-item";h.addEventListener("click",function(a){b(this.innerText)});m.appendChild(h)}}var f=this,l=document.createElement("div");l.className="graphdialog";l.innerHTML="Search
";l.close=function(){f.search_box=null;l.parentNode.removeChild(l)};l.addEventListener("mouseleave", +function(a){l.close()});f.search_box&&f.search_box.close();f.search_box=l;var m=l.querySelector(".helper"),g=null,q=null,p=null,r=l.querySelector("input");r&&r.addEventListener("keydown",function(a){if(38==a.keyCode)c(!1);else if(40==a.keyCode)c(!0);else if(27==a.keyCode)l.close();else if(13==a.keyCode)p?b(p.innerHTML):g?b(g):l.close();else{q&&clearInterval(q);q=setTimeout(d,10);return}a.preventDefault();a.stopPropagation()});var s=e.active_canvas,u=s.canvas,t=u.getBoundingClientRect(),z=-20,E=-20; +t&&(z-=t.left,E-=t.top);a?(l.style.left=a.pageX+z+"px",l.style.top=a.pageY+E+"px"):(l.style.left=0.5*u.width+z+"px",l.style.top=0.5*u.height+E+"px");u.parentNode.appendChild(l);r.focus();return l};e.prototype.showEditPropertyValue=function(a,b,c){function e(){f(q.value)}function f(c){"number"==typeof a.properties[b]&&(c=Number(c));"array"==l&&(c=c.split(",").map(Number));a.properties[b]=c;a._graph&&a._graph._version++;if(a.onPropertyChanged)a.onPropertyChanged(b,c);p.close();a.setDirtyCanvas(!0,!0)} +if(a&&void 0!==a.properties[b]){c=c||{};var l="string";null!==a.properties[b]&&(l=typeof a.properties[b]);"object"==l&&a.properties[b].length&&(l="array");var m=null;a.getPropertyInfo&&(m=a.getPropertyInfo(b));if(a.properties_info)for(var d=0;d";else if("enum"==l&&m.values){g= +""}else if("boolean"==l)g="";else{console.warn("unknown type: "+l);return}var p=this.createDialog(""+b+""+g+"",c);if("enum"==l&&m.values){var q=p.querySelector("select"); +q.addEventListener("change",function(a){f(a.target.value)})}else if("boolean"==l)(q=p.querySelector("input"))&&q.addEventListener("click",function(a){f(!!q.checked)});else if(q=p.querySelector("input"))q.value=void 0!==a.properties[b]?a.properties[b]:"",q.addEventListener("keydown",function(a){13==a.keyCode&&(e(),a.preventDefault(),a.stopPropagation())});p.querySelector("button").addEventListener("click",e)}};e.prototype.createDialog=function(a,b){b=b||{};var c=document.createElement("div");c.className= +"graphdialog";c.innerHTML=a;var e=this.canvas.getBoundingClientRect(),f=-20,l=-20;e&&(f-=e.left,l-=e.top);b.position?(f+=b.position[0],l+=b.position[1]):b.event?(f+=b.event.pageX,l+=b.event.pageY):(f+=0.5*this.canvas.width,l+=0.5*this.canvas.height);c.style.left=f+"px";c.style.top=l+"px";this.canvas.parentNode.appendChild(c);c.close=function(){this.parentNode&&this.parentNode.removeChild(this)};return c};e.onMenuNodeCollapse=function(a,b,c,e,f){f.collapse()};e.onMenuNodePin=function(a,b,c,e,f){f.pin()}; +e.onMenuNodeMode=function(a,b,c,e,f){new k.ContextMenu(["Always","On Event","On Trigger","Never"],{event:c,callback:function(a){if(f)switch(a){case "On Event":f.mode=k.ON_EVENT;break;case "On Trigger":f.mode=k.ON_TRIGGER;break;case "Never":f.mode=k.NEVER;break;default:f.mode=k.ALWAYS}},parentMenu:e,node:f});return!1};e.onMenuNodeColors=function(a,b,c,m,f){if(!f)throw"no node for color";b=[];b.push({value:null,content:"No color"});for(var l in e.node_colors)a= +e.node_colors[l],a={value:l,content:""+l+""},b.push(a);new k.ContextMenu(b,{event:c,callback:function(a){f&&((a=a.value?e.node_colors[a.value]:null)?f.constructor===k.LGraphGroup?f.color=a.groupcolor:(f.color=a.color,f.bgcolor=a.bgcolor):(delete f.color,delete f.bgcolor),f.setDirtyCanvas(!0,!0))},parentMenu:m,node:f});return!1};e.onMenuNodeShapes=function(a,b,c,e, +f){if(!f)throw"no node passed";new k.ContextMenu(k.VALID_SHAPES,{event:c,callback:function(a){f&&(f.shape=a,f.setDirtyCanvas(!0))},parentMenu:e,node:f});return!1};e.onMenuNodeRemove=function(a,b,c,e,f){if(!f)throw"no node passed";!1!==f.removable&&(f.graph.remove(f),f.setDirtyCanvas(!0,!0))};e.onMenuNodeClone=function(a,b,c,e,f){!1!=f.clonable&&(a=f.clone())&&(a.pos=[f.pos[0]+5,f.pos[1]+5],f.graph.add(a),f.setDirtyCanvas(!0,!0))};e.node_colors={red:{color:"#322",bgcolor:"#533",groupcolor:"#A88"}, +brown:{color:"#332922",bgcolor:"#593930",groupcolor:"#b06634"},green:{color:"#232",bgcolor:"#353",groupcolor:"#8A8"},blue:{color:"#223",bgcolor:"#335",groupcolor:"#88A"},pale_blue:{color:"#2a363b",bgcolor:"#3f5159",groupcolor:"#3f789e"},cyan:{color:"#233",bgcolor:"#355",groupcolor:"#8AA"},purple:{color:"#323",bgcolor:"#535",groupcolor:"#a1309b"},yellow:{color:"#432",bgcolor:"#653",groupcolor:"#b58b2a"},black:{color:"#222",bgcolor:"#000",groupcolor:"#444"}};e.prototype.getCanvasMenuOptions=function(){var a= +null;this.getMenuOptions?a=this.getMenuOptions():(a=[{content:"Add Node",has_submenu:!0,callback:e.onMenuAdd},{content:"Add Group",callback:e.onGroupAdd}],this._graph_stack&&0Name",f),m=d.querySelector("input");d.querySelector("button").addEventListener("click",function(b){if(m.value){if(b=e.input?a.getInputInfo(e.slot):a.getOutputInfo(e.slot))b.label=m.value;c.setDirty(!0)}d.close()})}},node:a},d=null;a&&(d=a.getSlotInPosition(b.canvasX,b.canvasY),e.active_node=a);d?(f=[],f.push(d.locked?"Cannot remove":{content:"Remove Slot",slot:d}),f.push({content:"Rename Slot",slot:d}),l.title= +(d.input?d.input.type:d.output.type)||"*",d.input&&d.input.type==k.ACTION&&(l.title="Action"),d.output&&d.output.type==k.EVENT&&(l.title="Event")):a?f=this.getNodeMenuOptions(a):(f=this.graph.getGroupOnPos(b.canvasX,b.canvasY))?(l.node=f,f=this.getGroupMenuOptions(f)):f=this.getCanvasMenuOptions();f&&new k.ContextMenu(f,l,m)};this.CanvasRenderingContext2D&&(CanvasRenderingContext2D.prototype.roundRect=function(a,b,c,e,f,l){void 0===f&&(f=5);void 0===l&&(l=f);this.moveTo(a+f,b);this.lineTo(a+c-f,b); +this.quadraticCurveTo(a+c,b,a+c,b+f);this.lineTo(a+c,b+e-l);this.quadraticCurveTo(a+c,b+e,a+c-l,b+e);this.lineTo(a+l,b+e);this.quadraticCurveTo(a,b+e,a,b+e-l);this.lineTo(a,b+f);this.quadraticCurveTo(a,b,a+f,b)});k.compareObjects=function(a,b){for(var c in a)if(a[c]!=b[c])return!1;return!0};k.distance=r;k.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")+")"};k.isInsideRectangle= +q;k.growBounding=function(a,b,c){ba[2]&&(a[2]=b);ca[3]&&(a[3]=c)};k.isInsideBounding=function(a,b){return a[0]b[1][0]||a[1]>b[1][1]?!1:!0};k.overlapBounding=s;k.hex2num=function(a){"#"==a.charAt(0)&&(a=a.slice(1));a=a.toUpperCase();for(var b=Array(3),c=0,e,f,l=0;6>l;l+=2)e="0123456789ABCDEF".indexOf(a.charAt(l)),f="0123456789ABCDEF".indexOf(a.charAt(l+1)),b[c]=16*e+f,c++;return b};k.num2hex=function(a){for(var b="#",c,e,f=0;3>f;f++)c=a[f]/ +16,e=a[f]%16,b+="0123456789ABCDEF".charAt(c)+"0123456789ABCDEF".charAt(e);return b};u.prototype.addItem=function(a,b,c){function e(a){var b=this.value;b&&b.has_submenu&&f.call(this,a)}function f(a){var b=this.value,f=!0;l.current_submenu&&l.current_submenu.close(a);if(c.callback){var e=c.callback.call(this,b,c,a,l,c.node);!0===e&&(f=!1)}if(b&&(b.callback&&!c.ignore_item_callbacks&&!0!==b.disabled&&(e=b.callback.call(this,b,c,a,l,c.node),!0===e&&(f=!1)),b.submenu)){if(!b.submenu.options)throw"ContextMenu submenu needs options"; +new l.constructor(b.submenu.options,{callback:b.submenu.callback,event:a,parentMenu:l,ignore_item_callbacks:b.submenu.ignore_item_callbacks,title:b.submenu.title,autoopen:c.autoopen});f=!1}f&&!l.lock&&l.close()}var l=this;c=c||{};var d=document.createElement("div");d.className="litemenu-entry submenu";var m=!1;if(null===b)d.classList.add("separator");else{d.innerHTML=b&&b.title?b.title:a;if(d.value=b)b.disabled&&(m=!0,d.classList.add("disabled")),(b.submenu||b.has_submenu)&&d.classList.add("has_submenu"); +"function"==typeof b?(d.dataset.value=a,d.onclick_callback=b):d.dataset.value=b;b.className&&(d.className+=" "+b.className)}this.root.appendChild(d);m||d.addEventListener("click",f);c.autoopen&&d.addEventListener("mouseenter",e);return d};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,e){var f=document.createEvent("CustomEvent");f.initCustomEvent(b,!0,!0,c);f.srcElement=e;a.dispatchEvent?a.dispatchEvent(f):a.__events&&a.__events.dispatchEvent(f);return f};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,e=a.pageY,f=b.getBoundingClientRect();return f?e>f.top&&ef.left&&ca?b:ca[1]))return this.old_y=c.canvasY,this.captureInput(!0),this.mouse_captured=!0};p.prototype.onMouseMove=function(c){if(this.mouse_captured){var a=this.old_y-c.canvasY;c.shiftKey&&(a*=10);if(c.metaKey||c.altKey)a*=0.1;this.old_y=c.canvasY;c=this._remainder+a/p.pixels_threshold;this._remainder=c%1;c=Math.clamp(this.properties.value+(c|0)*this.properties.step,this.properties.min,this.properties.max);this.properties.value=c;this.graph._version++; +this.setDirtyCanvas(!0)}};p.prototype.onMouseUp=function(c,a){200>c.click_time&&(this.properties.value=Math.clamp(this.properties.value+(a[1]>0.5*this.size[1]?-1:1)*this.properties.step,this.properties.min,this.properties.max),this.graph._version++,this.setDirtyCanvas(!0));this.mouse_captured&&(this.mouse_captured=!1,this.captureInput(!1))};m.registerNodeType("widget/number",p);e.title="Knob";e.desc="Circular controller";e.widgets=[{name:"increase",text:"+",type:"minibutton"},{name:"decrease",text:"-", +type:"minibutton"}];e.prototype.onAdded=function(){this.value=(this.properties.value-this.properties.min)/(this.properties.max-this.properties.min);this.imgbg=this.loadImage("imgs/knob_bg.png");this.imgfg=this.loadImage("imgs/knob_fg.png")};e.prototype.onDrawImageKnob=function(c){if(this.imgfg&&this.imgfg.width){var a=0.5*this.imgbg.width,b=this.size[0]/this.imgfg.width;c.save();c.translate(0,20);c.scale(b,b);c.drawImage(this.imgbg,0,0);c.translate(a,a);c.rotate(2*this.value*Math.PI*6/8+10*Math.PI/ +8);c.translate(-a,-a);c.drawImage(this.imgfg,0,0);c.restore();this.title&&(c.font="bold 16px Criticized,Tahoma",c.fillStyle="rgba(100,100,100,0.8)",c.textAlign="center",c.fillText(this.title.toUpperCase(),0.5*this.size[0],18),c.textAlign="left")}};e.prototype.onDrawVectorKnob=function(c){if(this.imgfg&&this.imgfg.width){c.lineWidth=1;c.strokeStyle=this.mouseOver?"#FFF":"#AAA";c.fillStyle="#000";c.beginPath();c.arc(0.5*this.size[0],0.5*this.size[1]+10,0.5*this.properties.size,0,2*Math.PI,!0);c.stroke(); +0c.canvasY-this.pos[1]||m.distance([c.canvasX,c.canvasY],[this.pos[0]+this.center[0],this.pos[1]+this.center[1]])>this.radius)return!1;this.oldmouse=[c.canvasX-this.pos[0],c.canvasY-this.pos[1]];this.captureInput(!0); +return!0}};e.prototype.onMouseMove=function(c){if(this.oldmouse){c=[c.canvasX-this.pos[0],c.canvasY-this.pos[1]];var a=this.value,a=a-0.01*(c[1]-this.oldmouse[1]);1a&&(a=0);this.value=a;this.properties.value=this.properties.min+(this.properties.max-this.properties.min)*this.value;this.oldmouse=c;this.setDirtyCanvas(!0)}};e.prototype.onMouseUp=function(c){this.oldmouse&&(this.oldmouse=null,this.captureInput(!1))};e.prototype.onMouseLeave=function(c){};e.prototype.onWidget=function(c,a){if("increase"== +a.name)this.onPropertyChanged("size",this.properties.size+10);else if("decrease"==a.name)this.onPropertyChanged("size",this.properties.size-10)};e.prototype.onPropertyChanged=function(c,a){if("wcolor"==c)this.properties[c]=a;else if("size"==c)a=parseInt(a),this.properties[c]=a,this.size=[a+4,a+24],this.setDirtyCanvas(!0,!0);else if("min"==c||"max"==c||"value"==c)this.properties[c]=parseFloat(a);else return!1;return!0};m.registerNodeType("widget/knob",e);r.title="Internal Slider";r.prototype.onPropertyChanged= +function(c,a){"value"==c&&(this.slider.value=a)};r.prototype.onExecute=function(){this.setOutputData(0,this.properties.value)};m.registerNodeType("widget/internal_slider",r);q.title="H.Slider";q.desc="Linear slider controller";q.prototype.onAdded=function(){this.value=0.5;this.imgfg=this.loadImage("imgs/slider_fg.png")};q.prototype.onDrawVectorial=function(c){this.imgfg&&this.imgfg.width&&(c.lineWidth=1,c.strokeStyle=this.mouseOver?"#FFF":"#AAA",c.fillStyle="#000",c.beginPath(),c.rect(2,0,this.size[0]- +4,20),c.stroke(),c.fillStyle=this.properties.wcolor,c.beginPath(),c.rect(2+(this.size[0]-4-20)*this.value,0,20,20),c.fill())};q.prototype.onDrawImage=function(c){this.imgfg&&this.imgfg.width&&(c.lineWidth=1,c.fillStyle="#000",c.fillRect(2,9,this.size[0]-4,2),c.strokeStyle="#333",c.beginPath(),c.moveTo(2,9),c.lineTo(this.size[0]-4,9),c.stroke(),c.strokeStyle="#AAA",c.beginPath(),c.moveTo(2,11),c.lineTo(this.size[0]-4,11),c.stroke(),c.drawImage(this.imgfg,2+(this.size[0]-4)*this.value-0.5*this.imgfg.width, +0.5*-this.imgfg.height+10))};q.prototype.onDrawForeground=function(c){this.onDrawImage(c)};q.prototype.onExecute=function(){this.properties.value=this.properties.min+(this.properties.max-this.properties.min)*this.value;this.setOutputData(0,this.properties.value);this.boxcolor=m.colorToString([this.value,this.value,this.value])};q.prototype.onMouseDown=function(c){if(0>c.canvasY-this.pos[1])return!1;this.oldmouse=[c.canvasX-this.pos[0],c.canvasY-this.pos[1]];this.captureInput(!0);return!0};q.prototype.onMouseMove= +function(c){if(this.oldmouse){c=[c.canvasX-this.pos[0],c.canvasY-this.pos[1]];var a=this.value,a=a+(c[0]-this.oldmouse[0])/this.size[0];1a&&(a=0);this.value=a;this.oldmouse=c;this.setDirtyCanvas(!0)}};q.prototype.onMouseUp=function(c){this.oldmouse=null;this.captureInput(!1)};q.prototype.onMouseLeave=function(c){};q.prototype.onPropertyChanged=function(c,a){if("wcolor"==c)this.properties[c]=a;else return!1;return!0};m.registerNodeType("widget/hslider",q);s.title="Progress";s.desc="Shows data in linear progress"; +s.prototype.onExecute=function(){var c=this.getInputData(0);void 0!=c&&(this.properties.value=c)};s.prototype.onDrawForeground=function(c){c.lineWidth=1;c.fillStyle=this.properties.wcolor;var a=(this.properties.value-this.properties.min)/(this.properties.max-this.properties.min),a=Math.min(1,a),a=Math.max(0,a);c.fillRect(2,2,(this.size[0]-4)*a,this.size[1]-4)};m.registerNodeType("widget/progress",s);u.title="Text";u.desc="Shows the input value";u.widgets=[{name:"resize",text:"Resize box",type:"button"}, +{name:"led_text",text:"LED",type:"minibutton"},{name:"normal_text",text:"Normal",type:"minibutton"}];u.prototype.onDrawForeground=function(c){c.fillStyle=this.properties.color;var a=this.properties.value;this.properties.glowSize?(c.shadowColor=this.properties.color,c.shadowOffsetX=0,c.shadowOffsetY=0,c.shadowBlur=this.properties.glowSize):c.shadowColor="transparent";var b=this.properties.fontsize;c.textAlign=this.properties.align;c.font=b.toString()+"px "+this.properties.font;this.str="number"==typeof a? +a.toFixed(this.properties.decimals):a;if("string"==typeof this.str){var a=this.str.split("\\n"),e;for(e in a)c.fillText(a[e],"left"==this.properties.align?15:this.size[0]-15,-0.15*b+b*(parseInt(e)+1))}c.shadowColor="transparent";this.last_ctx=c;c.textAlign="left"};u.prototype.onExecute=function(){var c=this.getInputData(0);null!=c&&(this.properties.value=c)};u.prototype.resize=function(){if(this.last_ctx){var c=this.str.split("\\n");this.last_ctx.font=this.properties.fontsize+"px "+this.properties.font; +var a=0,b;for(b in c){var e=this.last_ctx.measureText(c[b]).width;ae?d.xbox.axes.lx:0,this._left_axis[1]=Math.abs(d.xbox.axes.ly)>e?d.xbox.axes.ly:0,this._right_axis[0]=Math.abs(d.xbox.axes.rx)>e?d.xbox.axes.rx:0,this._right_axis[1]=Math.abs(d.xbox.axes.ry)>e?d.xbox.axes.ry:0,this._triggers[0]=Math.abs(d.xbox.axes.ltrigger)>e?d.xbox.axes.ltrigger:0,this._triggers[1]=Math.abs(d.xbox.axes.rtrigger)>e?d.xbox.axes.rtrigger:0);if(this.outputs)for(e= +0;ed;d++)if(e[d]){d=e[d];e=this.xbox_mapping;e||(e=this.xbox_mapping= +{axes:[],buttons:{},hat:""});e.axes.lx=d.axes[0];e.axes.ly=d.axes[1];e.axes.rx=d.axes[2];e.axes.ry=d.axes[3];e.axes.ltrigger=d.buttons[6].value;e.axes.rtrigger=d.buttons[7].value;for(var g=0;g","string",{values:f.values});this.size=[60,40]}function l(){this.addInput("inc","number");this.addOutput("total","number");this.addProperty("increment",1);this.addProperty("value",0)}function n(){this.addInput("v","number");this.addOutput("sin","number");this.addProperty("amplitude",1);this.addProperty("offset",0);this.bgImageUrl="nodes/imgs/icon-sin.png"}function C(){this.addInput("vec2", +"vec2");this.addOutput("x","number");this.addOutput("y","number")}function y(){this.addInputs([["x","number"],["y","number"]]);this.addOutput("vec2","vec2");this.properties={x:0,y:0};this._data=new Float32Array(2)}function x(){this.addInput("vec3","vec3");this.addOutput("x","number");this.addOutput("y","number");this.addOutput("z","number")}function A(){this.addInputs([["x","number"],["y","number"],["z","number"]]);this.addOutput("vec3","vec3");this.properties={x:0,y:0,z:0};this._data=new Float32Array(3)} +function D(){this.addInput("vec4","vec4");this.addOutput("x","number");this.addOutput("y","number");this.addOutput("z","number");this.addOutput("w","number")}function B(){this.addInputs([["x","number"],["y","number"],["z","number"],["w","number"]]);this.addOutput("vec4","vec4");this.properties={x:0,y:0,z:0,w:0};this._data=new Float32Array(4)}var v=t.LiteGraph;g.title="Converter";g.desc="type A to type B";g.prototype.onExecute=function(){var a=this.getInputData(0);if(null!=a&&this.outputs)for(var b= +0;bb&&(this._current=0);for(var c=a=0;cb&&(b=1);this.properties.samples=Math.round(b);var c=this._values;this._values=new Float32Array(this.properties.samples);c.length<=this._values.length?this._values.set(c):this._values.set(c.subarray(0,this._values.length))};v.registerNodeType("math/average",a);b.title="TendTo";b.desc="moves the output value always closer to the input";b.prototype.onExecute=function(){var a=this.getInputData(0);null==a&&(a=0);var b=this.properties.factor;this._value=null==this._value?a:this._value* +(1-b)+a*b;this.setOutputData(0,this._value)};v.registerNodeType("math/tendTo",b);h.values="+-*/%^".split("");h.title="Operation";h.desc="Easy math operators";h["@OP"]={type:"enum",title:"operation",values:h.values};h.prototype.getTitle=function(){return"A "+this.properties.OP+" B"};h.prototype.setValue=function(a){"string"==typeof a&&(a=parseFloat(a));this.properties.value=a};h.prototype.onExecute=function(){var a=this.getInputData(0),b=this.getInputData(1);null!=a?this.properties.A=a:a=this.properties.A; +null!=b?this.properties.B=b:b=this.properties.B;var c=0;switch(this.properties.OP){case "+":c=a+b;break;case "-":c=a-b;break;case "x":case "X":case "*":c=a*b;break;case "/":c=a/b;break;case "%":c=a%b;break;case "^":c=Math.pow(a,b);break;default:console.warn("Unknown operation: "+this.properties.OP)}this.setOutputData(0,c)};h.prototype.onDrawBackground=function(a){this.flags.collapsed||(a.font="40px Arial",a.fillStyle="#CCC",a.textAlign="center",a.fillText(this.properties.OP,0.5*this.size[0],0.35* +this.size[1]+v.NODE_TITLE_HEIGHT),a.textAlign="left")};v.registerNodeType("math/operation",h);w.title="Compare";w.desc="compares between two values";w.prototype.onExecute=function(){var a=this.getInputData(0),b=this.getInputData(1);void 0!==a?this.properties.A=a:a=this.properties.A;void 0!==b?this.properties.B=b:b=this.properties.B;for(var c=0,d=this.outputs.length;cB":value= +a>b;break;case "A=B":value=a>=b}this.setOutputData(c,value)}}};w.prototype.onGetOutputs=function(){return[["A==B","boolean"],["A!=B","boolean"],["A>B","boolean"],["A=B","boolean"],["A<=B","boolean"]]};v.registerNodeType("math/compare",w);f.values="> < == != <= >=".split(" ");f["@OP"]={type:"enum",title:"operation",values:f.values};f.title="Condition";f.desc="evaluates condition between A and B";f.prototype.onExecute=function(){var a= +this.getInputData(0);void 0===a?a=this.properties.A:this.properties.A=a;var b=this.getInputData(1);void 0===b?b=this.properties.B:this.properties.B=b;var c=!0;switch(this.properties.OP){case ">":c=a>b;break;case "<":c=a=":c=a>=b}this.setOutputData(0,c)};v.registerNodeType("math/condition",f);l.title="Accumulate";l.desc="Increments a value every time";l.prototype.onExecute=function(){null===this.properties.value&& +(this.properties.value=0);var a=this.getInputData(0);this.properties.value=null!==a?this.properties.value+a:this.properties.value+this.properties.increment;this.setOutputData(0,this.properties.value)};v.registerNodeType("math/accumulate",l);n.title="Trigonometry";n.desc="Sin Cos Tan";n.filter="shader";n.prototype.onExecute=function(){var a=this.getInputData(0);null==a&&(a=0);var b=this.properties.amplitude,c=this.findInputSlot("amplitude");-1!=c&&(b=this.getInputData(c));var d=this.properties.offset, +c=this.findInputSlot("offset");-1!=c&&(d=this.getInputData(c));for(var c=0,f=this.outputs.length;cXY";C.desc="vector 2 to components";C.prototype.onExecute=function(){var a=this.getInputData(0);null!=a&&(this.setOutputData(0, +a[0]),this.setOutputData(1,a[1]))};v.registerNodeType("math3d/vec2-to-xyz",C);y.title="XY->Vec2";y.desc="components to vector2";y.prototype.onExecute=function(){var a=this.getInputData(0);null==a&&(a=this.properties.x);var b=this.getInputData(1);null==b&&(b=this.properties.y);var c=this._data;c[0]=a;c[1]=b;this.setOutputData(0,c)};v.registerNodeType("math3d/xy-to-vec2",y);x.title="Vec3->XYZ";x.desc="vector 3 to components";x.prototype.onExecute=function(){var a=this.getInputData(0);null!=a&&(this.setOutputData(0, +a[0]),this.setOutputData(1,a[1]),this.setOutputData(2,a[2]))};v.registerNodeType("math3d/vec3-to-xyz",x);A.title="XYZ->Vec3";A.desc="components to vector3";A.prototype.onExecute=function(){var a=this.getInputData(0);null==a&&(a=this.properties.x);var b=this.getInputData(1);null==b&&(b=this.properties.y);var c=this.getInputData(2);null==c&&(c=this.properties.z);var d=this._data;d[0]=a;d[1]=b;d[2]=c;this.setOutputData(0,d)};v.registerNodeType("math3d/xyz-to-vec3",A);D.title="Vec4->XYZW";D.desc="vector 4 to components"; +D.prototype.onExecute=function(){var a=this.getInputData(0);null!=a&&(this.setOutputData(0,a[0]),this.setOutputData(1,a[1]),this.setOutputData(2,a[2]),this.setOutputData(3,a[3]))};v.registerNodeType("math3d/vec4-to-xyzw",D);B.title="XYZW->Vec4";B.desc="components to vector4";B.prototype.onExecute=function(){var a=this.getInputData(0);null==a&&(a=this.properties.x);var b=this.getInputData(1);null==b&&(b=this.properties.y);var c=this.getInputData(2);null==c&&(c=this.properties.z);var d=this.getInputData(3); +null==d&&(d=this.properties.w);var f=this._data;f[0]=a;f[1]=b;f[2]=c;f[3]=d;this.setOutputData(0,f)};v.registerNodeType("math3d/xyzw-to-vec4",B);t.glMatrix&&(t=function(){this.addOutput("quat","quat");this.properties={x:0,y:0,z:0,w:1};this._value=quat.create()},t.title="Quaternion",t.desc="quaternion",t.prototype.onExecute=function(){this._value[0]=this.properties.x;this._value[1]=this.properties.y;this._value[2]=this.properties.z;this._value[3]=this.properties.w;this.setOutputData(0,this._value)}, +v.registerNodeType("math3d/quaternion",t),t=function(){this.addInputs([["degrees","number"],["axis","vec3"]]);this.addOutput("quat","quat");this.properties={angle:90,axis:vec3.fromValues(0,1,0)};this._value=quat.create()},t.title="Rotation",t.desc="quaternion rotation",t.prototype.onExecute=function(){var a=this.getInputData(0);null==a&&(a=this.properties.angle);var b=this.getInputData(1);null==b&&(b=this.properties.axis);a=quat.setAxisAngle(this._value,b,0.0174532925*a);this.setOutputData(0,a)}, +v.registerNodeType("math3d/rotation",t),t=function(){this.addInputs([["vec3","vec3"],["quat","quat"]]);this.addOutput("result","vec3");this.properties={vec:[0,0,1]}},t.title="Rot. Vec3",t.desc="rotate a point",t.prototype.onExecute=function(){var a=this.getInputData(0);null==a&&(a=this.properties.vec);var b=this.getInputData(1);null==b?this.setOutputData(a):this.setOutputData(0,vec3.transformQuat(vec3.create(),a,b))},v.registerNodeType("math3d/rotate_vec3",t),t=function(){this.addInputs([["A","quat"], +["B","quat"]]);this.addOutput("A*B","quat");this._value=quat.create()},t.title="Mult. Quat",t.desc="rotate quaternion",t.prototype.onExecute=function(){var a=this.getInputData(0);if(null!=a){var b=this.getInputData(1);null!=b&&(a=quat.multiply(this._value,a,b),this.setOutputData(0,a))}},v.registerNodeType("math3d/mult-quat",t),t=function(){this.addInputs([["A","quat"],["B","quat"],["factor","number"]]);this.addOutput("slerp","quat");this.addProperty("factor",0.5);this._value=quat.create()},t.title= +"Quat Slerp",t.desc="quaternion spherical interpolation",t.prototype.onExecute=function(){var a=this.getInputData(0);if(null!=a){var b=this.getInputData(1);if(null!=b){var c=this.properties.factor;null!=this.getInputData(2)&&(c=this.getInputData(2));a=quat.slerp(this._value,a,b,c);this.setOutputData(0,a)}}},v.registerNodeType("math3d/quat-slerp",t))})(this); +(function(t){function g(){this.addInput("sel","boolean");this.addOutput("value","number");this.properties={A:0,B:1};this.size=[60,20]}t=t.LiteGraph;g.title="Selector";g.desc="outputs A if selector is true, B if selector is false";g.prototype.onExecute=function(){var d=this.getInputData(0);if(void 0!==d){for(var g=1;gc;++c){var a=this.getInputData(c);if(null!=a){var b=this.values[c];b.push(a);b.length>d[0]&&b.shift()}}}};g.prototype.onDrawBackground=function(d){if(!this.flags.collapsed){var c=this.size,a=0.5*c[1]/this.properties.scale,b=g.colors,e=0.5*c[1];d.fillStyle="#000";d.fillRect(0,0,c[0],c[1]);d.strokeStyle="#555";d.beginPath();d.moveTo(0,e);d.lineTo(c[0], +e);d.stroke();for(var k=0;4>k;++k){var f=this.values[k];d.strokeStyle=b[k];d.beginPath();var l=f[0]*a*-1+e;d.moveTo(0,Math.clamp(l,0,c[1]));for(var n=1;nc&&(c=0);if(0!=d.length){var a=[0,0,0];if(0==c)a=d[0];else if(1==c)a=d[d.length-1];else{var b=(d.length-1)*c,c=d[Math.floor(b)],d=d[Math.floor(b)+1],b=b-Math.floor(b);a[0]=c[0]*(1-b)+d[0]*b;a[1]=c[1]*(1-b)+d[1]*b;a[2]=c[2]*(1-b)+d[2]*b}for(var e in a)a[e]/=255;this.boxcolor=colorToString(a);this.setOutputData(0,a)}};k.registerNodeType("color/palette",p);e.title="Frame";e.desc="Frame viewerew";e.widgets=[{name:"resize",text:"Resize box",type:"button"}, +{name:"view",text:"View Image",type:"button"}];e.prototype.onDrawBackground=function(d){this.frame&&d.drawImage(this.frame,0,0,this.size[0],this.size[1])};e.prototype.onExecute=function(){this.frame=this.getInputData(0);this.setDirtyCanvas(!0)};e.prototype.onWidget=function(d,c){if("resize"==c.name&&this.frame){var a=this.frame.width,b=this.frame.height;a||null==this.frame.videoWidth||(a=this.frame.videoWidth,b=this.frame.videoHeight);a&&b&&(this.size=[a,b]);this.setDirtyCanvas(!0,!0)}else"view"== +c.name&&this.show()};e.prototype.show=function(){showElement&&this.frame&&showElement(this.frame)};k.registerNodeType("graphics/frame",e);r.title="Image fade";r.desc="Fades between images";r.widgets=[{name:"resizeA",text:"Resize to A",type:"button"},{name:"resizeB",text:"Resize to B",type:"button"}];r.prototype.onAdded=function(){this.createCanvas();var d=this.canvas.getContext("2d");d.fillStyle="#000";d.fillRect(0,0,this.properties.width,this.properties.height)};r.prototype.createCanvas=function(){this.canvas= +document.createElement("canvas");this.canvas.width=this.properties.width;this.canvas.height=this.properties.height};r.prototype.onExecute=function(){var d=this.canvas.getContext("2d");this.canvas.width=this.canvas.width;var c=this.getInputData(0);null!=c&&d.drawImage(c,0,0,this.canvas.width,this.canvas.height);c=this.getInputData(2);null==c?c=this.properties.fade:this.properties.fade=c;d.globalAlpha=c;c=this.getInputData(1);null!=c&&d.drawImage(c,0,0,this.canvas.width,this.canvas.height);d.globalAlpha= +1;this.setOutputData(0,this.canvas);this.setDirtyCanvas(!0)};k.registerNodeType("graphics/imagefade",r);q.title="Crop";q.desc="Crop Image";q.prototype.onAdded=function(){this.createCanvas()};q.prototype.createCanvas=function(){this.canvas=document.createElement("canvas");this.canvas.width=this.properties.width;this.canvas.height=this.properties.height};q.prototype.onExecute=function(){var d=this.getInputData(0);d&&(d.width?(this.canvas.getContext("2d").drawImage(d,-this.properties.x,-this.properties.y, +d.width*this.properties.scale,d.height*this.properties.scale),this.setOutputData(0,this.canvas)):this.setOutputData(0,null))};q.prototype.onDrawBackground=function(d){this.flags.collapsed||this.canvas&&d.drawImage(this.canvas,0,0,this.canvas.width,this.canvas.height,0,0,this.size[0],this.size[1])};q.prototype.onPropertyChanged=function(d,c){this.properties[d]=c;"scale"==d?(this.properties[d]=parseFloat(c),0==this.properties[d]&&(this.trace("Error in scale"),this.properties[d]=1)):this.properties[d]= +parseInt(c);this.createCanvas();return!0};k.registerNodeType("graphics/cropImage",q);s.title="Video";s.desc="Video playback";s.widgets=[{name:"play",text:"PLAY",type:"minibutton"},{name:"stop",text:"STOP",type:"minibutton"},{name:"demo",text:"Demo video",type:"button"},{name:"mute",text:"Mute video",type:"button"}];s.prototype.onExecute=function(){if(this.properties.url&&(this.properties.url!=this._video_url&&this.loadVideo(this.properties.url),this._video&&0!=this._video.width)){var d=this.getInputData(0); +d&&0<=d&&1>=d&&(this._video.currentTime=d*this._video.duration,this._video.pause());this._video.dirty=!0;this.setOutputData(0,this._video);this.setOutputData(1,this._video.currentTime);this.setOutputData(2,this._video.duration);this.setDirtyCanvas(!0)}};s.prototype.onStart=function(){this.play()};s.prototype.onStop=function(){this.stop()};s.prototype.loadVideo=function(d){this._video_url=d;this.properties.use_proxy&&"http"==d.substr(0,4)&&k.proxy&&(d=k.proxy+d.substr(d.indexOf(":")+3));this._video= +document.createElement("video");this._video.src=d;this._video.type="type=video/mp4";this._video.muted=!0;this._video.autoplay=!0;var c=this;this._video.addEventListener("loadedmetadata",function(a){c.trace("Duration: "+this.duration+" seconds");c.trace("Size: "+this.videoWidth+","+this.videoHeight);c.setDirtyCanvas(!0);this.width=this.videoWidth;this.height=this.videoHeight});this._video.addEventListener("progress",function(a){});this._video.addEventListener("error",function(a){console.log("Error loading video: "+ +this.src);c.trace("Error loading video: "+this.src);if(this.error)switch(this.error.code){case this.error.MEDIA_ERR_ABORTED:c.trace("You stopped the video.");break;case this.error.MEDIA_ERR_NETWORK:c.trace("Network error - please try again later.");break;case this.error.MEDIA_ERR_DECODE:c.trace("Video is broken..");break;case this.error.MEDIA_ERR_SRC_NOT_SUPPORTED:c.trace("Sorry, your browser can't play this video.")}});this._video.addEventListener("ended",function(a){c.trace("Ended.");this.play()})}; +s.prototype.onPropertyChanged=function(d,c){this.properties[d]=c;"url"==d&&""!=c&&this.loadVideo(c);return!0};s.prototype.play=function(){this._video&&this._video.play()};s.prototype.playPause=function(){this._video&&(this._video.paused?this.play():this.pause())};s.prototype.stop=function(){this._video&&(this._video.pause(),this._video.currentTime=0)};s.prototype.pause=function(){this._video&&(this.trace("Video paused"),this._video.pause())};s.prototype.onWidget=function(d,c){};k.registerNodeType("graphics/video", +s);u.title="Webcam";u.desc="Webcam image";u.prototype.openStream=function(){function d(a){console.log("Webcam rejected",a);c._webcam_stream=!1;c.box_color="red"}navigator.getUserMedia=navigator.getUserMedia||navigator.webkitGetUserMedia||navigator.mozGetUserMedia||navigator.msGetUserMedia;window.URL=window.URL||window.webkitURL;if(navigator.getUserMedia){this._waiting_confirmation=!0;navigator.getUserMedia({video:!0},this.streamReady.bind(this),d);var c=this}};u.prototype.onRemoved=function(){this._webcam_stream&& +(this._webcam_stream.stop(),this._video=this._webcam_stream=null)};u.prototype.streamReady=function(d){this._webcam_stream=d;var c=this._video;c||(c=document.createElement("video"),c.autoplay=!0,c.src=window.URL.createObjectURL(d),this._video=c,c.onloadedmetadata=function(a){console.log(a)})};u.prototype.onExecute=function(){null!=this._webcam_stream||this._waiting_confirmation||this.openStream();this._video&&this._video.videoWidth&&(this._video.width=this._video.videoWidth,this._video.height=this._video.videoHeight, +this.setOutputData(0,this._video))};u.prototype.getExtraMenuOptions=function(d){var c=this;return[{content:c.properties.show?"Hide Frame":"Show Frame",callback:function(){c.properties.show=!c.properties.show}}]};u.prototype.onDrawBackground=function(d){this.flags.collapsed||20>=this.size[1]||!this.properties.show||!this._video||(d.save(),d.drawImage(this._video,0,0,this.size[0],this.size[1]),d.restore())};k.registerNodeType("graphics/webcam",u)})(this); +(function(t){var g=t.LiteGraph;t.LGraphTexture=null;if("undefined"!=typeof GL){var d=function(){this.addOutput("Texture","Texture");this.properties={name:"",filter:!0};this.size=[d.image_preview_size,d.image_preview_size]};t.LGraphTexture=d;d.title="Texture";d.desc="Texture";d.widgets_info={name:{widget:"texture"},filter:{widget:"checkbox"}};d.loadTextureCallback=null;d.image_preview_size=256;d.PASS_THROUGH=1;d.COPY=2;d.LOW=3;d.HIGH=4;d.REUSE=5;d.DEFAULT=2;d.MODE_VALUES={"pass through":d.PASS_THROUGH, +copy:d.COPY,low:d.LOW,high:d.HIGH,reuse:d.REUSE,"default":d.DEFAULT};d.getTexturesContainer=function(){return gl.textures};d.loadTexture=function(a,b){b=b||{};var c=a;"http://"==c.substr(0,7)&&g.proxy&&(c=g.proxy+c.substr(7));return d.getTexturesContainer()[a]=GL.Texture.fromURL(c,b)};d.getTexture=function(a){var b=this.getTexturesContainer();if(!b)throw"Cannot load texture, container of textures not found";b=b[a];return!b&&a&&":"!=a[0]?this.loadTexture(a):b};d.getTargetTexture=function(a,b,c){if(!a)throw"LGraphTexture.getTargetTexture expects a reference texture"; +var f=null;switch(c){case d.LOW:f=gl.UNSIGNED_BYTE;break;case d.HIGH:f=gl.HIGH_PRECISION_FORMAT;break;case d.REUSE:return a;default:f=a?a.type:gl.UNSIGNED_BYTE}b&&b.width==a.width&&b.height==a.height&&b.type==f||(b=new GL.Texture(a.width,a.height,{type:f,format:gl.RGBA,filter:gl.LINEAR}));return b};d.getTextureType=function(a,b){var c=b?b.type:gl.UNSIGNED_BYTE;switch(a){case d.HIGH:c=gl.HIGH_PRECISION_FORMAT;break;case d.LOW:c=gl.UNSIGNED_BYTE}return c};d.getNoiseTexture=function(){if(this._noise_texture)return this._noise_texture; +for(var a=new Uint8Array(1048576),b=0;1048576>b;++b)a[b]=255*Math.random();return this._noise_texture=a=GL.Texture.fromMemory(512,512,a,{format:gl.RGBA,wrap:gl.REPEAT,filter:gl.NEAREST})};d.prototype.onDropFile=function(a,b,c){if(a){var d=null;"string"==typeof a?d=GL.Texture.fromURL(a):-1!=b.toLowerCase().indexOf(".dds")?d=GL.Texture.fromDDSInMemory(a):(a=new Blob([c]),a=URL.createObjectURL(a),d=GL.Texture.fromURL(a));this._drop_texture=d;this.properties.name=b}else this._drop_texture=null,this.properties.name= +""};d.prototype.getExtraMenuOptions=function(a){var b=this;if(this._drop_texture)return[{content:"Clear",callback:function(){b._drop_texture=null;b.properties.name=""}}]};d.prototype.onExecute=function(){var a=null;this.isOutputConnected(1)&&(a=this.getInputData(0));!a&&this._drop_texture&&(a=this._drop_texture);!a&&this.properties.name&&(a=d.getTexture(this.properties.name));if(a){this._last_tex=a;!1===this.properties.filter?a.setParameter(gl.TEXTURE_MAG_FILTER,gl.NEAREST):a.setParameter(gl.TEXTURE_MAG_FILTER, +gl.LINEAR);this.setOutputData(0,a);for(var b=1;b=this.size[1]))if(this._drop_texture&&a.webgl)a.drawImage(this._drop_texture,0,0,this.size[0],this.size[1]); +else{if(this._last_preview_tex!=this._last_tex)if(a.webgl)this._canvas=this._last_tex;else{var b=d.generateLowResTexturePreview(this._last_tex);if(!b)return;this._last_preview_tex=this._last_tex;this._canvas=cloneCanvas(b)}this._canvas&&(a.save(),a.webgl||(a.translate(0,this.size[1]),a.scale(1,-1)),a.drawImage(this._canvas,0,0,this.size[0],this.size[1]),a.restore())}};d.generateLowResTexturePreview=function(a){if(!a)return null;var b=d.image_preview_size,c=a;if(a.format==gl.DEPTH_COMPONENT)return null; +if(a.width>b||a.height>b)c=this._preview_temp_tex,this._preview_temp_tex||(this._preview_temp_tex=c=new GL.Texture(b,b,{minFilter:gl.NEAREST})),a.copyTo(c);a=this._preview_canvas;a||(this._preview_canvas=a=createCanvas(b,b));c&&c.toCanvas(a);return a};d.prototype.getResources=function(a){a[this.properties.name]=GL.Texture;return a};d.prototype.onGetInputs=function(){return[["in","Texture"]]};d.prototype.onGetOutputs=function(){return[["width","number"],["height","number"],["aspect","number"]]};g.registerNodeType("texture/texture", +d);var p=function(){this.addInput("Texture","Texture");this.properties={flipY:!1};this.size=[d.image_preview_size,d.image_preview_size]};p.title="Preview";p.desc="Show a texture in the graph canvas";p.allow_preview=!1;p.prototype.onDrawBackground=function(a){if(!this.flags.collapsed&&(a.webgl||p.allow_preview)){var b=this.getInputData(0);if(b){var c=null,c=!b.handle&&a.webgl?b:d.generateLowResTexturePreview(b);a.save();this.properties.flipY&&(a.translate(0,this.size[1]),a.scale(1,-1));a.drawImage(c, +0,0,this.size[0],this.size[1]);a.restore()}}};g.registerNodeType("texture/preview",p);var e=function(){this.addInput("Texture","Texture");this.addOutput("","Texture");this.properties={name:""}};e.title="Save";e.desc="Save a texture in the repository";e.prototype.onExecute=function(){var a=this.getInputData(0);a&&(this.properties.name&&(d.storeTexture?d.storeTexture(this.properties.name,a):d.getTexturesContainer()[this.properties.name]=a),this.setOutputData(0,a))};g.registerNodeType("texture/save", +e);var r=function(){this.addInput("Texture","Texture");this.addInput("TextureB","Texture");this.addInput("value","number");this.addOutput("Texture","Texture");this.help="

pixelcode must be vec3

\t\t\t

uvcode must be vec2, is optional

\t\t\t

uv: tex. coords

color: texture

colorB: textureB

time: scene time

value: input value

";this.properties={value:1,uvcode:"",pixelcode:"color + colorB * value", +precision:d.DEFAULT}};r.widgets_info={uvcode:{widget:"textarea",height:100},pixelcode:{widget:"textarea",height:100},precision:{widget:"combo",values:d.MODE_VALUES}};r.title="Operation";r.desc="Texture shader operation";r.prototype.getExtraMenuOptions=function(a){var b=this;return[{content:b.properties.show?"Hide Texture":"Show Texture",callback:function(){b.properties.show=!b.properties.show}}]};r.prototype.onDrawBackground=function(a){this.flags.collapsed||20>=this.size[1]||!this.properties.show|| +!this._tex||this._tex.gl!=a||(a.save(),a.drawImage(this._tex,0,0,this.size[0],this.size[1]),a.restore())};r.prototype.onExecute=function(){var a=this.getInputData(0);if(this.isOutputConnected(0))if(this.properties.precision===d.PASS_THROUGH)this.setOutputData(0,a);else{var b=this.getInputData(1);if(this.properties.uvcode||this.properties.pixelcode){var c=512,f=512;a?(c=a.width,f=a.height):b&&(c=b.width,f=b.height);var e=d.getTextureType(this.properties.precision,a);this._tex=a||this._tex?d.getTargetTexture(a|| +this._tex,this._tex,this.properties.precision):new GL.Texture(c,f,{type:e,format:gl.RGBA,filter:gl.LINEAR});e="";this.properties.uvcode&&(e="uv = "+this.properties.uvcode,-1!=this.properties.uvcode.indexOf(";")&&(e=this.properties.uvcode));var l="";this.properties.pixelcode&&(l="result = "+this.properties.pixelcode,-1!=this.properties.pixelcode.indexOf(";")&&(l=this.properties.pixelcode));var h=this._shader;if(!h||this._shader_code!=e+"|"+l){try{this._shader=new GL.Shader(Shader.SCREEN_VERTEX_SHADER, +r.pixel_shader,{UV_CODE:e,PIXEL_CODE:l}),this.boxcolor="#00FF00"}catch(g){console.log("Error compiling shader: ",g);this.boxcolor="#FF0000";return}this.boxcolor="#FF0000";this._shader_code=e+"|"+l;h=this._shader}if(h){this.boxcolor="green";var k=this.getInputData(2);null!=k?this.properties.value=k:k=parseFloat(this.properties.value);var m=this.graph.getTime();this._tex.drawTo(function(){gl.disable(gl.DEPTH_TEST);gl.disable(gl.CULL_FACE);gl.disable(gl.BLEND);a&&a.bind(0);b&&b.bind(1);var d=Mesh.getScreenQuad(); +h.uniforms({u_texture:0,u_textureB:1,value:k,texSize:[c,f],time:m}).draw(d)});this.setOutputData(0,this._tex)}else this.boxcolor="red"}}};r.pixel_shader="precision highp float;\n\t\t\t\n\t\t\tuniform sampler2D u_texture;\n\t\t\tuniform sampler2D u_textureB;\n\t\t\tvarying vec2 v_coord;\n\t\t\tuniform vec2 texSize;\n\t\t\tuniform float time;\n\t\t\tuniform float value;\n\t\t\t\n\t\t\tvoid main() {\n\t\t\t\tvec2 uv = v_coord;\n\t\t\t\tUV_CODE;\n\t\t\t\tvec4 color4 = texture2D(u_texture, uv);\n\t\t\t\tvec3 color = color4.rgb;\n\t\t\t\tvec4 color4B = texture2D(u_textureB, uv);\n\t\t\t\tvec3 colorB = color4B.rgb;\n\t\t\t\tvec3 result = color;\n\t\t\t\tfloat alpha = 1.0;\n\t\t\t\tPIXEL_CODE;\n\t\t\t\tgl_FragColor = vec4(result, alpha);\n\t\t\t}\n\t\t\t"; +g.registerNodeType("texture/operation",r);var q=function(){this.addOutput("out","Texture");this.properties={code:"",width:512,height:512,precision:d.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={in_texture:0,texSize:vec2.create(),time:0}};q.title="Shader";q.desc="Texture shader";q.widgets_info={code:{type:"code"},precision:{widget:"combo",values:d.MODE_VALUES}};q.prototype.onPropertyChanged= +function(a,b){if("code"==a){var c=this.getShader();if(c){var d=c.uniformInfo;if(this.inputs)for(var f={},e=0;e lumaMax))\n\t\t\t\t\tcolor = vec4(rgbA, 1.0);\n\t\t\t\telse\n\t\t\t\t\tcolor = vec4(rgbB, 1.0);\n\t\t\t\tif(u_igamma != 1.0)\n\t\t\t\t\tcolor.xyz = pow( color.xyz, vec3(u_igamma) );\n\t\t\t\treturn color;\n\t\t\t}\n\t\t\t\n\t\t\tvoid main() {\n\t\t\t gl_FragColor = applyFXAA( u_texture, v_coord * uViewportSize) ;\n\t\t\t}\n\t\t\t"; +k.gamma_pixel_shader="precision highp float;\n\t\t\tprecision highp float;\n\t\t\tvarying vec2 v_coord;\n\t\t\tuniform sampler2D u_texture;\n\t\t\tuniform float u_igamma;\n\t\t\tvoid main() {\n\t\t\t\tvec4 color = texture2D( u_texture, v_coord);\n\t\t\t\tcolor.xyz = pow(color.xyz, vec3(u_igamma) );\n\t\t\t gl_FragColor = color;\n\t\t\t}\n\t\t\t";g.registerNodeType("texture/toviewport",k);e=function(){this.addInput("Texture","Texture");this.addOutput("","Texture");this.properties={size:0,generate_mipmaps:!1, +precision:d.DEFAULT}};e.title="Copy";e.desc="Copy Texture";e.widgets_info={size:{widget:"combo",values:[0,32,64,128,256,512,1024,2048]},precision:{widget:"combo",values:d.MODE_VALUES}};e.prototype.onExecute=function(){var a=this.getInputData(0);if((a||this._temp_texture)&&this.isOutputConnected(0)){if(a){var b=a.width,c=a.height;0!=this.properties.size&&(c=b=this.properties.size);var f=this._temp_texture,e=a.type;this.properties.precision===d.LOW?e=gl.UNSIGNED_BYTE:this.properties.precision===d.HIGH&& +(e=gl.HIGH_PRECISION_FORMAT);f&&f.width==b&&f.height==c&&f.type==e||(f=gl.LINEAR,this.properties.generate_mipmaps&&isPowerOfTwo(b)&&isPowerOfTwo(c)&&(f=gl.LINEAR_MIPMAP_LINEAR),this._temp_texture=new GL.Texture(b,c,{type:e,format:gl.RGBA,minFilter:f,magFilter:gl.LINEAR}));a.copyTo(this._temp_texture);this.properties.generate_mipmaps&&(this._temp_texture.bind(0),gl.generateMipmap(this._temp_texture.texture_type),this._temp_texture.unbind(0))}this.setOutputData(0,this._temp_texture)}};g.registerNodeType("texture/copy", +e);var m=function(){this.addInput("Texture","Texture");this.addOutput("","Texture");this.properties={iterations:1,generate_mipmaps:!1,precision:d.DEFAULT}};m.title="Downsample";m.desc="Downsample Texture";m.widgets_info={iterations:{type:"number",step:1,precision:0,min:1},precision:{widget:"combo",values:d.MODE_VALUES}};m.prototype.onExecute=function(){var a=this.getInputData(0);if((a||this._temp_texture)&&this.isOutputConnected(0)&&a&&a.texture_type===GL.TEXTURE_2D){var b=m._shader;b||(m._shader= +b=new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER,m.pixel_shader));var c=a.width|0,f=a.height|0,e=a.type;this.properties.precision===d.LOW?e=gl.UNSIGNED_BYTE:this.properties.precision===d.HIGH&&(e=gl.HIGH_PRECISION_FORMAT);var l=this.properties.iterations||1,h=a,g=null,k=[],a={type:e,format:a.format},e=vec2.create(),n={u_offset:e};this._texture&&GL.Texture.releaseTemporary(this._texture);for(var q=0;q>1||0;f=f>>1||0;g=GL.Texture.getTemporary(c,f,a);k.push(g);h.setParameter(GL.TEXTURE_MAG_FILTER, +GL.NEAREST);h.copyTo(g,b,n);if(1==c&&1==f)break;h=g}this._texture=k.pop();for(q=0;qd;++d)b[d]=Math.random();c._shader.uniforms({u_samples_a:b.subarray(0,16),u_samples_b:b.subarray(16,32)})}d=this._temp_texture;b=gl.UNSIGNED_BYTE;a.type!=b&&(b=gl.FLOAT);d&&d.type==b||(this._temp_texture=new GL.Texture(1,1,{type:b,format:gl.RGBA,filter:gl.NEAREST})); +var f=c._shader,e=this._uniforms;e.u_mipmap_offset=this.properties.mipmap_offset;this._temp_texture.drawTo(function(){a.toViewport(f,e)});this.setOutputData(0,this._temp_texture);if(this.isOutputConnected(1)||this.isOutputConnected(2))if(d=this._temp_texture.getPixels()){var l=this._luminance,b=this._temp_texture.type;l.set(d);b==gl.UNSIGNED_BYTE?vec4.scale(l,l,1/255):b!=GL.HALF_FLOAT&&b!=GL.HALF_FLOAT_OES||vec4.scale(l,l,1/65025);this.setOutputData(1,l);this.setOutputData(2,(l[0]+l[1]+l[2])/3)}}}; +c.pixel_shader="precision highp float;\n\t\t\tprecision highp float;\n\t\t\tuniform mat4 u_samples_a;\n\t\t\tuniform mat4 u_samples_b;\n\t\t\tuniform sampler2D u_texture;\n\t\t\tuniform float u_mipmap_offset;\n\t\t\tvarying vec2 v_coord;\n\t\t\t\n\t\t\tvoid main() {\n\t\t\t\tvec4 color = vec4(0.0);\n\t\t\t\tfor(int i = 0; i < 4; ++i)\n\t\t\t\t\tfor(int j = 0; j < 4; ++j)\n\t\t\t\t\t{\n\t\t\t\t\t\tcolor += texture2D(u_texture, vec2( u_samples_a[i][j], u_samples_b[i][j] ), u_mipmap_offset );\n\t\t\t\t\t\tcolor += texture2D(u_texture, vec2( 1.0 - u_samples_a[i][j], 1.0 - u_samples_b[i][j] ), u_mipmap_offset );\n\t\t\t\t\t}\n\t\t\t gl_FragColor = color * 0.03125;\n\t\t\t}\n\t\t\t"; +g.registerNodeType("texture/average",c);e=function(){this.addInput("Image","image");this.addOutput("","Texture");this.properties={}};e.title="Image to Texture";e.desc="Uploads an image to the GPU";e.prototype.onExecute=function(){var a=this.getInputData(0);if(a){var b=a.videoWidth||a.width,c=a.videoHeight||a.height;if(a.gltexture)this.setOutputData(0,a.gltexture);else{var d=this._temp_texture;d&&d.width==b&&d.height==c||(this._temp_texture=new GL.Texture(b,c,{format:gl.RGBA,filter:gl.LINEAR}));try{this._temp_texture.uploadImage(a)}catch(f){console.error("image comes from an unsafe location, cannot be uploaded to webgl: "+ +f);return}this.setOutputData(0,this._temp_texture)}}};g.registerNodeType("texture/imageToTexture",e);var a=function(){this.addInput("Texture","Texture");this.addInput("LUT","Texture");this.addInput("Intensity","number");this.addOutput("","Texture");this.properties={intensity:1,precision:d.DEFAULT,texture:null};a._shader||(a._shader=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,a.pixel_shader))};a.widgets_info={texture:{widget:"texture"},precision:{widget:"combo",values:d.MODE_VALUES}};a.title="LUT";a.desc= +"Apply LUT to Texture";a.prototype.onExecute=function(){if(this.isOutputConnected(0)){var b=this.getInputData(0);if(this.properties.precision===d.PASS_THROUGH)this.setOutputData(0,b);else if(b){var c=this.getInputData(1);c||(c=d.getTexture(this.properties.texture));if(c){c.bind(0);gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MIN_FILTER,gl.LINEAR);gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_WRAP_S,gl.CLAMP_TO_EDGE);gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_WRAP_T,gl.CLAMP_TO_EDGE);gl.bindTexture(gl.TEXTURE_2D, +null);var f=this.properties.intensity;this.isInputConnected(2)&&(this.properties.intensity=f=this.getInputData(2));this._tex=d.getTargetTexture(b,this._tex,this.properties.precision);this._tex.drawTo(function(){c.bind(1);b.toViewport(a._shader,{u_texture:0,u_textureB:1,u_amount:f})});this.setOutputData(0,this._tex)}else this.setOutputData(0,b)}}};a.pixel_shader="precision highp float;\n\t\t\tprecision highp float;\n\t\t\tvarying vec2 v_coord;\n\t\t\tuniform sampler2D u_texture;\n\t\t\tuniform sampler2D u_textureB;\n\t\t\tuniform float u_amount;\n\t\t\t\n\t\t\tvoid main() {\n\t\t\t\t lowp vec4 textureColor = clamp( texture2D(u_texture, v_coord), vec4(0.0), vec4(1.0) );\n\t\t\t\t mediump float blueColor = textureColor.b * 63.0;\n\t\t\t\t mediump vec2 quad1;\n\t\t\t\t quad1.y = floor(floor(blueColor) / 8.0);\n\t\t\t\t quad1.x = floor(blueColor) - (quad1.y * 8.0);\n\t\t\t\t mediump vec2 quad2;\n\t\t\t\t quad2.y = floor(ceil(blueColor) / 8.0);\n\t\t\t\t quad2.x = ceil(blueColor) - (quad2.y * 8.0);\n\t\t\t\t highp vec2 texPos1;\n\t\t\t\t texPos1.x = (quad1.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r);\n\t\t\t\t texPos1.y = 1.0 - ((quad1.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.g));\n\t\t\t\t highp vec2 texPos2;\n\t\t\t\t texPos2.x = (quad2.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r);\n\t\t\t\t texPos2.y = 1.0 - ((quad2.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.g));\n\t\t\t\t lowp vec4 newColor1 = texture2D(u_textureB, texPos1);\n\t\t\t\t lowp vec4 newColor2 = texture2D(u_textureB, texPos2);\n\t\t\t\t lowp vec4 newColor = mix(newColor1, newColor2, fract(blueColor));\n\t\t\t\t gl_FragColor = vec4( mix( textureColor.rgb, newColor.rgb, u_amount), textureColor.w);\n\t\t\t}\n\t\t\t"; +g.registerNodeType("texture/LUT",a);var b=function(){this.addInput("Texture","Texture");this.addOutput("R","Texture");this.addOutput("G","Texture");this.addOutput("B","Texture");this.addOutput("A","Texture");this.properties={};b._shader||(b._shader=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,b.pixel_shader))};b.title="Texture to Channels";b.desc="Split texture channels";b.prototype.onExecute=function(){var a=this.getInputData(0);if(a){this._channels||(this._channels=Array(4));for(var c=0,d=0;4>d;d++)this.isOutputConnected(d)? +(this._channels[d]&&this._channels[d].width==a.width&&this._channels[d].height==a.height&&this._channels[d].type==a.type||(this._channels[d]=new GL.Texture(a.width,a.height,{type:a.type,format:gl.RGBA,filter:gl.LINEAR})),c++):this._channels[d]=null;if(c){gl.disable(gl.BLEND);gl.disable(gl.DEPTH_TEST);for(var f=Mesh.getScreenQuad(),e=b._shader,l=[[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]],d=0;4>d;d++)this._channels[d]&&(this._channels[d].drawTo(function(){a.bind(0);e.uniforms({u_texture:0,u_mask:l[d]}).draw(f)}), +this.setOutputData(d,this._channels[d]))}}};b.pixel_shader="precision highp float;\n\t\t\tprecision highp float;\n\t\t\tvarying vec2 v_coord;\n\t\t\tuniform sampler2D u_texture;\n\t\t\tuniform vec4 u_mask;\n\t\t\t\n\t\t\tvoid main() {\n\t\t\t gl_FragColor = vec4( vec3( length( texture2D(u_texture, v_coord) * u_mask )), 1.0 );\n\t\t\t}\n\t\t\t";g.registerNodeType("texture/textureChannels",b);var h=function(){this.addInput("R","Texture");this.addInput("G","Texture");this.addInput("B","Texture");this.addInput("A", +"Texture");this.addOutput("Texture","Texture");this.properties={};h._shader||(h._shader=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,h.pixel_shader))};h.title="Channels to Texture";h.desc="Split texture channels";h.prototype.onExecute=function(){var a=[this.getInputData(0),this.getInputData(1),this.getInputData(2),this.getInputData(3)];if(a[0]&&a[1]&&a[2]&&a[3]){gl.disable(gl.BLEND);gl.disable(gl.DEPTH_TEST);var b=Mesh.getScreenQuad(),c=h._shader;this._tex=d.getTargetTexture(a[0],this._tex);this._tex.drawTo(function(){a[0].bind(0); +a[1].bind(1);a[2].bind(2);a[3].bind(3);c.uniforms({u_textureR:0,u_textureG:1,u_textureB:2,u_textureA:3}).draw(b)});this.setOutputData(0,this._tex)}};h.pixel_shader="precision highp float;\n\t\t\tprecision highp float;\n\t\t\tvarying vec2 v_coord;\n\t\t\tuniform sampler2D u_textureR;\n\t\t\tuniform sampler2D u_textureG;\n\t\t\tuniform sampler2D u_textureB;\n\t\t\tuniform sampler2D u_textureA;\n\t\t\t\n\t\t\tvoid main() {\n\t\t\t gl_FragColor = vec4( \t\t\t\t\t\ttexture2D(u_textureR, v_coord).r,\t\t\t\t\t\ttexture2D(u_textureG, v_coord).r,\t\t\t\t\t\ttexture2D(u_textureB, v_coord).r,\t\t\t\t\t\ttexture2D(u_textureA, v_coord).r);\n\t\t\t}\n\t\t\t"; +g.registerNodeType("texture/channelsTexture",h);var w=function(){this.addInput("A","color");this.addInput("B","color");this.addOutput("Texture","Texture");this.properties={angle:0,scale:1,A:[0,0,0],B:[1,1,1],texture_size:32};w._shader||(w._shader=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,w.pixel_shader));this._uniforms={u_angle:0,u_colorA:vec3.create(),u_colorB:vec3.create()}};w.title="Gradient";w.desc="Generates a gradient";w["@A"]={type:"color"};w["@B"]={type:"color"};w["@texture_size"]={type:"enum", +values:[32,64,128,256,512]};w.prototype.onExecute=function(){gl.disable(gl.BLEND);gl.disable(gl.DEPTH_TEST);var a=GL.Mesh.getScreenQuad(),b=w._shader,c=this.getInputData(0);c||(c=this.properties.A);var d=this.getInputData(1);d||(d=this.properties.B);for(var f=2;f 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\t\t\t}\n\t\t\t"; +g.registerNodeType("texture/edges",l);var n=function(){this.addInput("Texture","Texture");this.addInput("Distance","number");this.addInput("Range","number");this.addOutput("Texture","Texture");this.properties={distance:100,range:50,only_depth:!1,high_precision:!1};this._uniforms={u_texture:0,u_distance:100,u_range:50,u_camera_planes:null}};n.title="Depth Range";n.desc="Generates a texture with a depth range";n.prototype.onExecute=function(){if(this.isOutputConnected(0)){var a=this.getInputData(0); +if(a){var b=gl.UNSIGNED_BYTE;this.properties.high_precision&&(b=gl.half_float_ext?gl.HALF_FLOAT_OES:gl.FLOAT);this._temp_texture&&this._temp_texture.type==b&&this._temp_texture.width==a.width&&this._temp_texture.height==a.height||(this._temp_texture=new GL.Texture(a.width,a.height,{type:b,format:gl.RGBA,filter:gl.LINEAR}));var c=this._uniforms,b=this.properties.distance;this.isInputConnected(1)&&(b=this.getInputData(1),this.properties.distance=b);var d=this.properties.range;this.isInputConnected(2)&& +(d=this.getInputData(2),this.properties.range=d);c.u_distance=b;c.u_range=d;gl.disable(gl.BLEND);gl.disable(gl.DEPTH_TEST);var f=Mesh.getScreenQuad();n._shader||(n._shader=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,n.pixel_shader),n._shader_onlydepth=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,n.pixel_shader,{ONLY_DEPTH:""}));var e=this.properties.only_depth?n._shader_onlydepth:n._shader,b=null,b=a.near_far_planes?a.near_far_planes:window.LS&&LS.Renderer._main_camera?LS.Renderer._main_camera._uniforms.u_camera_planes: +[0.1,1E3];c.u_camera_planes=b;this._temp_texture.drawTo(function(){a.bind(0);e.uniforms(c).draw(f)});this._temp_texture.near_far_planes=b;this.setOutputData(0,this._temp_texture)}}};n.pixel_shader="precision highp float;\n\t\t\tprecision highp float;\n\t\t\tvarying vec2 v_coord;\n\t\t\tuniform sampler2D u_texture;\n\t\t\tuniform vec2 u_camera_planes;\n\t\t\tuniform float u_distance;\n\t\t\tuniform float u_range;\n\t\t\t\n\t\t\tfloat LinearDepth()\n\t\t\t{\n\t\t\t\tfloat zNear = u_camera_planes.x;\n\t\t\t\tfloat zFar = u_camera_planes.y;\n\t\t\t\tfloat depth = texture2D(u_texture, v_coord).x;\n\t\t\t\tdepth = depth * 2.0 - 1.0;\n\t\t\t\treturn zNear * (depth + 1.0) / (zFar + zNear - depth * (zFar - zNear));\n\t\t\t}\n\t\t\t\n\t\t\tvoid main() {\n\t\t\t\tfloat depth = LinearDepth();\n\t\t\t\t#ifdef ONLY_DEPTH\n\t\t\t\t gl_FragColor = vec4(depth);\n\t\t\t\t#else\n\t\t\t\t\tfloat diff = abs(depth * u_camera_planes.y - u_distance);\n\t\t\t\t\tfloat dof = 1.0;\n\t\t\t\t\tif(diff <= u_range)\n\t\t\t\t\t\tdof = diff / u_range;\n\t\t\t\t gl_FragColor = vec4(dof);\n\t\t\t\t#endif\n\t\t\t}\n\t\t\t"; +g.registerNodeType("texture/depth_range",n);var C=function(){this.addInput("Texture","Texture");this.addInput("Iterations","number");this.addInput("Intensity","number");this.addOutput("Blurred","Texture");this.properties={intensity:1,iterations:1,preserve_aspect:!1,scale:[1,1],precision:d.DEFAULT}};C.title="Blur";C.desc="Blur a texture";C.widgets_info={precision:{widget:"combo",values:d.MODE_VALUES}};C.max_iterations=20;C.prototype.onExecute=function(){var a=this.getInputData(0);if(a&&this.isOutputConnected(0)){var b= +this._final_texture;b&&b.width==a.width&&b.height==a.height&&b.type==a.type||(b=this._final_texture=new GL.Texture(a.width,a.height,{type:a.type,format:gl.RGBA,filter:gl.LINEAR}));var c=this.properties.iterations;this.isInputConnected(1)&&(c=this.getInputData(1),this.properties.iterations=c);c=Math.min(Math.floor(c),C.max_iterations);if(0==c)this.setOutputData(0,a);else{var d=this.properties.intensity;this.isInputConnected(2)&&(d=this.getInputData(2),this.properties.intensity=d);var f=g.camera_aspect; +f||void 0===window.gl||(f=gl.canvas.height/gl.canvas.width);f||(f=1);var f=this.properties.preserve_aspect?f:1,e=this.properties.scale||[1,1];a.applyBlur(f*e[0],e[1],d,b);for(a=1;a>=1;1<(c|0)&&(c>>=1);if(2>b)break;k=h[p]=GL.Texture.getTemporary(b,c,f);q[0]=1/m.width;q[1]=1/m.height;m.blit(k,g.uniforms(l));m=k}this.isOutputConnected(2)&&(b=this._average_texture,b&&b.type==a.type&&b.format==a.format||(b=this._average_texture=new GL.Texture(1,1,{type:a.type,format:a.format,filter:gl.LINEAR})),q[0]=1/m.width,q[1]=1/m.height,l.u_intensity= +r,l.u_delta=1,m.blit(b,g.uniforms(l)),this.setOutputData(2,b));gl.enable(gl.BLEND);gl.blendFunc(gl.ONE,gl.ONE);l.u_intensity=this.getInputOrProperty("persistence");l.u_delta=0.5;for(p-=2;0<=p;p--)k=h[p],h[p]=null,q[0]=1/m.width,q[1]=1/m.height,m.blit(k,g.uniforms(l)),GL.Texture.releaseTemporary(m),m=k;gl.disable(gl.BLEND);this.isOutputConnected(1)&&(h=this._glow_texture,h&&h.width==a.width&&h.height==a.height&&h.type==e&&h.format==a.format||(h=this._glow_texture=new GL.Texture(a.width,a.height,{type:e, +format:a.format,filter:gl.LINEAR})),m.blit(h),this.setOutputData(1,h));if(this.isOutputConnected(0)){h=this._final_texture;h&&h.width==a.width&&h.height==a.height&&h.type==e&&h.format==a.format||(h=this._final_texture=new GL.Texture(a.width,a.height,{type:e,format:a.format,filter:gl.LINEAR}));var s=this.getInputData(1),C=this.getInputOrProperty("dirt_factor");l.u_intensity=r;g=s?y._dirt_final_shader:y._final_shader;g||(g=s?y._dirt_final_shader=new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER,y.final_pixel_shader, +{USE_DIRT:""}):y._final_shader=new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER,y.final_pixel_shader));h.drawTo(function(){a.bind(0);m.bind(1);s&&(g.setUniform("u_dirt_factor",C),g.setUniform("u_dirt_texture",s.bind(2)));g.toViewport(l)});this.setOutputData(0,h)}GL.Texture.releaseTemporary(m)}};y.cut_pixel_shader="precision highp float;\n\t\tvarying vec2 v_coord;\n\t\tuniform sampler2D u_texture;\n\t\tuniform float u_threshold;\n\t\tvoid main() {\n\t\t\tgl_FragColor = max( texture2D( u_texture, v_coord ) - vec4( u_threshold ), vec4(0.0) );\n\t\t}"; +y.scale_pixel_shader="precision highp float;\n\t\tvarying vec2 v_coord;\n\t\tuniform sampler2D u_texture;\n\t\tuniform vec2 u_texel_size;\n\t\tuniform float u_delta;\n\t\tuniform float u_intensity;\n\t\t\n\t\tvec4 sampleBox(vec2 uv) {\n\t\t\tvec4 o = u_texel_size.xyxy * vec2(-u_delta, u_delta).xxyy;\n\t\t\tvec4 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\t\t\treturn s * 0.25;\n\t\t}\n\t\tvoid main() {\n\t\t\tgl_FragColor = u_intensity * sampleBox( v_coord );\n\t\t}"; +y.final_pixel_shader="precision highp float;\n\t\tvarying vec2 v_coord;\n\t\tuniform sampler2D u_texture;\n\t\tuniform sampler2D u_glow_texture;\n\t\t#ifdef USE_DIRT\n\t\t\tuniform sampler2D u_dirt_texture;\n\t\t#endif\n\t\tuniform vec2 u_texel_size;\n\t\tuniform float u_delta;\n\t\tuniform float u_intensity;\n\t\tuniform float u_dirt_factor;\n\t\t\n\t\tvec4 sampleBox(vec2 uv) {\n\t\t\tvec4 o = u_texel_size.xyxy * vec2(-u_delta, u_delta).xxyy;\n\t\t\tvec4 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\t\t\treturn s * 0.25;\n\t\t}\n\t\tvoid main() {\n\t\t\tvec4 glow = sampleBox( v_coord );\n\t\t\t#ifdef USE_DIRT\n\t\t\t\tglow = mix( glow, glow * texture2D( u_dirt_texture, v_coord ), u_dirt_factor );\n\t\t\t#endif\n\t\t\tgl_FragColor = texture2D( u_texture, v_coord ) + u_intensity * glow;\n\t\t}"; +g.registerNodeType("texture/glow",y);var x=function(){this.addInput("Texture","Texture");this.addOutput("Filtered","Texture");this.properties={intensity:1,radius:5}};x.title="Kuwahara Filter";x.desc="Filters a texture giving an artistic oil canvas painting";x.max_radius=10;x._shaders=[];x.prototype.onExecute=function(){var a=this.getInputData(0);if(a&&this.isOutputConnected(0)){var b=this._temp_texture;b&&b.width==a.width&&b.height==a.height&&b.type==a.type||(this._temp_texture=new GL.Texture(a.width, +a.height,{type:a.type,format:gl.RGBA,filter:gl.LINEAR}));b=this.properties.radius;b=Math.min(Math.floor(b),x.max_radius);if(0==b)this.setOutputData(0,a);else{var c=this.properties.intensity,d=g.camera_aspect;d||void 0===window.gl||(d=gl.canvas.height/gl.canvas.width);d||(d=1);d=this.properties.preserve_aspect?d:1;x._shaders[b]||(x._shaders[b]=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,x.pixel_shader,{RADIUS:b.toFixed(0)}));var f=x._shaders[b],e=GL.Mesh.getScreenQuad();a.bind(0);this._temp_texture.drawTo(function(){f.uniforms({u_texture:0, +u_intensity:c,u_resolution:[a.width,a.height],u_iResolution:[1/a.width,1/a.height]}).draw(e)});this.setOutputData(0,this._temp_texture)}}};x.pixel_shader="\n\tprecision highp float;\n\tvarying vec2 v_coord;\n\tuniform sampler2D u_texture;\n\tuniform float u_intensity;\n\tuniform vec2 u_resolution;\n\tuniform vec2 u_iResolution;\n\t#ifndef RADIUS\n\t\t#define RADIUS 7\n\t#endif\n\tvoid main() {\n\t\n\t\tconst int radius = RADIUS;\n\t\tvec2 fragCoord = v_coord;\n\t\tvec2 src_size = u_iResolution;\n\t\tvec2 uv = v_coord;\n\t\tfloat n = float((radius + 1) * (radius + 1));\n\t\tint i;\n\t\tint j;\n\t\tvec3 m0 = vec3(0.0); vec3 m1 = vec3(0.0); vec3 m2 = vec3(0.0); vec3 m3 = vec3(0.0);\n\t\tvec3 s0 = vec3(0.0); vec3 s1 = vec3(0.0); vec3 s2 = vec3(0.0); vec3 s3 = vec3(0.0);\n\t\tvec3 c;\n\t\t\n\t\tfor (int j = -radius; j <= 0; ++j) {\n\t\t\tfor (int i = -radius; i <= 0; ++i) {\n\t\t\t\tc = texture2D(u_texture, uv + vec2(i,j) * src_size).rgb;\n\t\t\t\tm0 += c;\n\t\t\t\ts0 += c * c;\n\t\t\t}\n\t\t}\n\t\t\n\t\tfor (int j = -radius; j <= 0; ++j) {\n\t\t\tfor (int i = 0; i <= radius; ++i) {\n\t\t\t\tc = texture2D(u_texture, uv + vec2(i,j) * src_size).rgb;\n\t\t\t\tm1 += c;\n\t\t\t\ts1 += c * c;\n\t\t\t}\n\t\t}\n\t\t\n\t\tfor (int j = 0; j <= radius; ++j) {\n\t\t\tfor (int i = 0; i <= radius; ++i) {\n\t\t\t\tc = texture2D(u_texture, uv + vec2(i,j) * src_size).rgb;\n\t\t\t\tm2 += c;\n\t\t\t\ts2 += c * c;\n\t\t\t}\n\t\t}\n\t\t\n\t\tfor (int j = 0; j <= radius; ++j) {\n\t\t\tfor (int i = -radius; i <= 0; ++i) {\n\t\t\t\tc = texture2D(u_texture, uv + vec2(i,j) * src_size).rgb;\n\t\t\t\tm3 += c;\n\t\t\t\ts3 += c * c;\n\t\t\t}\n\t\t}\n\t\t\n\t\tfloat min_sigma2 = 1e+2;\n\t\tm0 /= n;\n\t\ts0 = abs(s0 / n - m0 * m0);\n\t\t\n\t\tfloat sigma2 = s0.r + s0.g + s0.b;\n\t\tif (sigma2 < min_sigma2) {\n\t\t\tmin_sigma2 = sigma2;\n\t\t\tgl_FragColor = vec4(m0, 1.0);\n\t\t}\n\t\t\n\t\tm1 /= n;\n\t\ts1 = abs(s1 / n - m1 * m1);\n\t\t\n\t\tsigma2 = s1.r + s1.g + s1.b;\n\t\tif (sigma2 < min_sigma2) {\n\t\t\tmin_sigma2 = sigma2;\n\t\t\tgl_FragColor = vec4(m1, 1.0);\n\t\t}\n\t\t\n\t\tm2 /= n;\n\t\ts2 = abs(s2 / n - m2 * m2);\n\t\t\n\t\tsigma2 = s2.r + s2.g + s2.b;\n\t\tif (sigma2 < min_sigma2) {\n\t\t\tmin_sigma2 = sigma2;\n\t\t\tgl_FragColor = vec4(m2, 1.0);\n\t\t}\n\t\t\n\t\tm3 /= n;\n\t\ts3 = abs(s3 / n - m3 * m3);\n\t\t\n\t\tsigma2 = s3.r + s3.g + s3.b;\n\t\tif (sigma2 < min_sigma2) {\n\t\t\tmin_sigma2 = sigma2;\n\t\t\tgl_FragColor = vec4(m3, 1.0);\n\t\t}\n\t}\n\t"; +g.registerNodeType("texture/kuwahara",x);e=function(){this.addOutput("Webcam","Texture");this.properties={texture_name:""}};e.title="Webcam";e.desc="Webcam texture";e.prototype.openStream=function(){function a(c){console.log("Webcam rejected",c);b._webcam_stream=!1;b.box_color="red"}navigator.getUserMedia=navigator.getUserMedia||navigator.webkitGetUserMedia||navigator.mozGetUserMedia||navigator.msGetUserMedia;window.URL=window.URL||window.webkitURL;if(navigator.getUserMedia){this._waiting_confirmation= +!0;var b=this;navigator.getUserMedia({video:!0},this.streamReady.bind(this),a)}};e.prototype.streamReady=function(a){this._webcam_stream=a;var b=this._video;b||(b=document.createElement("video"),b.autoplay=!0,b.src=window.URL.createObjectURL(a),this._video=b,b.onloadedmetadata=function(a){console.log(a)})};e.prototype.onRemoved=function(){if(this._webcam_stream){var a=this._webcam_stream.getVideoTracks();a.length&&(a=a[0],a.stop&&a.stop());this._video=this._webcam_stream=null}};e.prototype.onDrawBackground= +function(a){this.flags.collapsed||20>=this.size[1]||!this._video||(a.save(),a.webgl?this._temp_texture&&a.drawImage(this._temp_texture,0,0,this.size[0],this.size[1]):(a.translate(0,this.size[1]),a.scale(1,-1),a.drawImage(this._video,0,0,this.size[0],this.size[1])),a.restore())};e.prototype.onExecute=function(){null!=this._webcam_stream||this._waiting_confirmation||this.openStream();if(this._video&&this._video.videoWidth){var a=this._video.videoWidth,b=this._video.videoHeight,c=this._temp_texture; +c&&c.width==a&&c.height==b||(this._temp_texture=new GL.Texture(a,b,{format:gl.RGB,filter:gl.LINEAR}));this._temp_texture.uploadImage(this._video);this.properties.texture_name&&(d.getTexturesContainer()[this.properties.texture_name]=this._temp_texture);this.setOutputData(0,this._temp_texture)}};g.registerNodeType("texture/webcam",e);var A=function(){this.addInput("in","Texture");this.addInput("f","number");this.addOutput("out","Texture");this.properties={enabled:!0,factor:1,precision:d.LOW};this._uniforms= +{u_texture:0,u_factor:1}};A.title="Lens FX";A.desc="distortion and chromatic aberration";A.widgets_info={precision:{widget:"combo",values:d.MODE_VALUES}};A.prototype.onGetInputs=function(){return[["enabled","boolean"]]};A.prototype.onExecute=function(){var a=this.getInputData(0);if(a&&this.isOutputConnected(0))if(this.properties.precision===d.PASS_THROUGH||!1===this.getInputOrProperty("enabled"))this.setOutputData(0,a);else{var b=this._temp_texture;b&&b.width==a.width&&b.height==a.height&&b.type== +a.type||(b=this._temp_texture=new GL.Texture(a.width,a.height,{type:a.type,format:gl.RGBA,filter:gl.LINEAR}));var c=A._shader;c||(c=A._shader=new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER,A.pixel_shader));var f=this.getInputData(1);null==f&&(f=this.properties.factor);var e=this._uniforms;e.u_factor=f;gl.disable(gl.DEPTH_TEST);b.drawTo(function(){a.bind(0);c.uniforms(e).draw(GL.Mesh.getScreenQuad())});this.setOutputData(0,b)}};A.pixel_shader="precision highp float;\n\t\t\tvarying vec2 v_coord;\n\t\t\tuniform sampler2D u_texture;\n\t\t\tuniform float u_factor;\n\t\t\tvec2 barrelDistortion(vec2 coord, float amt) {\n\t\t\t\tvec2 cc = coord - 0.5;\n\t\t\t\tfloat dist = dot(cc, cc);\n\t\t\t\treturn coord + cc * dist * amt;\n\t\t\t}\n\t\t\t\n\t\t\tfloat sat( float t )\n\t\t\t{\n\t\t\t\treturn clamp( t, 0.0, 1.0 );\n\t\t\t}\n\t\t\t\n\t\t\tfloat linterp( float t ) {\n\t\t\t\treturn sat( 1.0 - abs( 2.0*t - 1.0 ) );\n\t\t\t}\n\t\t\t\n\t\t\tfloat remap( float t, float a, float b ) {\n\t\t\t\treturn sat( (t - a) / (b - a) );\n\t\t\t}\n\t\t\t\n\t\t\tvec4 spectrum_offset( float t ) {\n\t\t\t\tvec4 ret;\n\t\t\t\tfloat lo = step(t,0.5);\n\t\t\t\tfloat hi = 1.0-lo;\n\t\t\t\tfloat w = linterp( remap( t, 1.0/6.0, 5.0/6.0 ) );\n\t\t\t\tret = vec4(lo,1.0,hi, 1.) * vec4(1.0-w, w, 1.0-w, 1.);\n\t\t\t\n\t\t\t\treturn pow( ret, vec4(1.0/2.2) );\n\t\t\t}\n\t\t\t\n\t\t\tconst float max_distort = 2.2;\n\t\t\tconst int num_iter = 12;\n\t\t\tconst float reci_num_iter_f = 1.0 / float(num_iter);\n\t\t\t\n\t\t\tvoid main()\n\t\t\t{\t\n\t\t\t\tvec2 uv=v_coord;\n\t\t\t\tvec4 sumcol = vec4(0.0);\n\t\t\t\tvec4 sumw = vec4(0.0);\t\n\t\t\t\tfor ( int i=0; i=this.size[1]||a.webgl&&(gl.meshes.cube||(gl.meshes.cube=GL.Mesh.cube({size:1})))};g.registerNodeType("texture/cubemap",e)}})(this); +(function(t){var g=t.LiteGraph;if("undefined"!=typeof GL){var d=function(){this.addInput("Texture","Texture");this.addInput("Aberration","number");this.addInput("Distortion","number");this.addInput("Blur","number");this.addOutput("Texture","Texture");this.properties={aberration:1,distortion:1,blur:1,precision:LGraphTexture.DEFAULT};d._shader||(d._shader=new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER,d.pixel_shader),d._texture=new GL.Texture(3,1,{format:gl.RGB,wrap:gl.CLAMP_TO_EDGE,magFilter:gl.LINEAR, +minFilter:gl.LINEAR,pixel_data:[255,0,0,0,255,0,0,0,255]}))};d.title="Lens";d.desc="Camera Lens distortion";d.widgets_info={precision:{widget:"combo",values:LGraphTexture.MODE_VALUES}};d.prototype.onExecute=function(){var e=this.getInputData(0);if(this.properties.precision===LGraphTexture.PASS_THROUGH)this.setOutputData(0,e);else if(e){this._tex=LGraphTexture.getTargetTexture(e,this._tex,this.properties.precision);var g=this.properties.aberration;this.isInputConnected(1)&&(g=this.getInputData(1), +this.properties.aberration=g);var p=this.properties.distortion;this.isInputConnected(2)&&(p=this.getInputData(2),this.properties.distortion=p);var k=this.properties.blur;this.isInputConnected(3)&&(k=this.getInputData(3),this.properties.blur=k);gl.disable(gl.BLEND);gl.disable(gl.DEPTH_TEST);var m=Mesh.getScreenQuad(),c=d._shader;this._tex.drawTo(function(){e.bind(0);c.uniforms({u_texture:0,u_aberration:g,u_distortion:p,u_blur:k}).draw(m)});this.setOutputData(0,this._tex)}};d.pixel_shader="precision highp float;\n\t\t\tprecision highp float;\n\t\t\tvarying vec2 v_coord;\n\t\t\tuniform sampler2D u_texture;\n\t\t\tuniform vec2 u_camera_planes;\n\t\t\tuniform float u_aberration;\n\t\t\tuniform float u_distortion;\n\t\t\tuniform float u_blur;\n\t\t\t\n\t\t\tvoid main() {\n\t\t\t\tvec2 coord = v_coord;\n\t\t\t\tfloat dist = distance(vec2(0.5), coord);\n\t\t\t\tvec2 dist_coord = coord - vec2(0.5);\n\t\t\t\tfloat percent = 1.0 + ((0.5 - dist) / 0.5) * u_distortion;\n\t\t\t\tdist_coord *= percent;\n\t\t\t\tcoord = dist_coord + vec2(0.5);\n\t\t\t\tvec4 color = texture2D(u_texture,coord, u_blur * dist);\n\t\t\t\tcolor.r = texture2D(u_texture,vec2(0.5) + dist_coord * (1.0+0.01*u_aberration), u_blur * dist ).r;\n\t\t\t\tcolor.b = texture2D(u_texture,vec2(0.5) + dist_coord * (1.0-0.01*u_aberration), u_blur * dist ).b;\n\t\t\t\tgl_FragColor = color;\n\t\t\t}\n\t\t\t"; +g.registerNodeType("fx/lens",d);t.LGraphFXLens=d;var p=function(){this.addInput("Texture","Texture");this.addInput("Blurred","Texture");this.addInput("Mask","Texture");this.addInput("Threshold","number");this.addOutput("Texture","Texture");this.properties={shape:"",size:10,alpha:1,threshold:1,high_precision:!1}};p.title="Bokeh";p.desc="applies an Bokeh effect";p.widgets_info={shape:{widget:"texture"}};p.prototype.onExecute=function(){var d=this.getInputData(0),e=this.getInputData(1),g=this.getInputData(2); +if(d&&g&&this.properties.shape){e||(e=d);var k=LGraphTexture.getTexture(this.properties.shape);if(k){var m=this.properties.threshold;this.isInputConnected(3)&&(m=this.getInputData(3),this.properties.threshold=m);var c=gl.UNSIGNED_BYTE;this.properties.high_precision&&(c=gl.half_float_ext?gl.HALF_FLOAT_OES:gl.FLOAT);this._temp_texture&&this._temp_texture.type==c&&this._temp_texture.width==d.width&&this._temp_texture.height==d.height||(this._temp_texture=new GL.Texture(d.width,d.height,{type:c,format:gl.RGBA, +filter:gl.LINEAR}));var a=p._first_shader;a||(a=p._first_shader=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,p._first_pixel_shader));var b=p._second_shader;b||(b=p._second_shader=new GL.Shader(p._second_vertex_shader,p._second_pixel_shader));var h=this._points_mesh;h&&h._width==d.width&&h._height==d.height&&2==h._spacing||(h=this.createPointsMesh(d.width,d.height,2));var r=Mesh.getScreenQuad(),f=this.properties.size,l=this.properties.alpha;gl.disable(gl.DEPTH_TEST);gl.disable(gl.BLEND);this._temp_texture.drawTo(function(){d.bind(0); +e.bind(1);g.bind(2);a.uniforms({u_texture:0,u_texture_blur:1,u_mask:2,u_texsize:[d.width,d.height]}).draw(r)});this._temp_texture.drawTo(function(){gl.enable(gl.BLEND);gl.blendFunc(gl.ONE,gl.ONE);d.bind(0);k.bind(3);b.uniforms({u_texture:0,u_mask:2,u_shape:3,u_alpha:l,u_threshold:m,u_pointSize:f,u_itexsize:[1/d.width,1/d.height]}).draw(h,gl.POINTS)});this.setOutputData(0,this._temp_texture)}}else this.setOutputData(0,d)};p.prototype.createPointsMesh=function(d,e,g){for(var k=Math.round(d/g),m=Math.round(e/ +g),c=new Float32Array(k*m*2),a=-1,b=2/d*g,h=2/e*g,p=0;p=g.NOTEON||c<=g.NOTEOFF)this.channel=d&15};Object.defineProperty(g.prototype,"velocity",{get:function(){return this.cmd==g.NOTEON?this.data[2]: +-1},set:function(d){this.data[2]=d},enumerable:!0});g.notes="A A# B C C# D D# E F F# G G#".split(" ");g.prototype.getPitch=function(){return 440*Math.pow(2,(this.data[1]-69)/12)};g.computePitch=function(d){return 440*Math.pow(2,(d-69)/12)};g.prototype.getCC=function(){return this.data[1]};g.prototype.getCCValue=function(){return this.data[2]};g.prototype.getPitchBend=function(){return this.data[1]+(this.data[2]<<7)-8192};g.computePitchBend=function(d,c){return d+(c<<7)-8192};g.prototype.setCommandFromString= +function(d){this.cmd=g.computeCommandFromString(d)};g.computeCommandFromString=function(d){if(!d)return 0;if(d&&d.constructor===Number)return d;d=d.toUpperCase();switch(d){case "NOTE ON":case "NOTEON":return g.NOTEON;case "NOTE OFF":case "NOTEOFF":return g.NOTEON;case "KEY PRESSURE":case "KEYPRESSURE":return g.KEYPRESSURE;case "CONTROLLER CHANGE":case "CONTROLLERCHANGE":case "CC":return g.CONTROLLERCHANGE;case "PROGRAM CHANGE":case "PROGRAMCHANGE":case "PC":return g.PROGRAMCHANGE;case "CHANNEL PRESSURE":case "CHANNELPRESSURE":return g.CHANNELPRESSURE; +case "PITCH BEND":case "PITCHBEND":return g.PITCHBEND;case "TIME TICK":case "TIMETICK":return g.TIMETICK;default:return Number(d)}};g.toNoteString=function(d){var c;c=(d-21)%12;0>c&&(c=12+c);return g.notes[c]+Math.floor((d-24)/12+1)};g.prototype.toString=function(){var d=""+this.channel+". ";switch(this.cmd){case g.NOTEON:d+="NOTEON "+g.toNoteString(this.data[1]);break;case g.NOTEOFF:d+="NOTEOFF "+g.toNoteString(this.data[1]);break;case g.CONTROLLERCHANGE:d+="CC "+this.data[1]+" "+this.data[2];break; +case g.PROGRAMCHANGE:d+="PC "+this.data[1];break;case g.PITCHBEND:d+="PITCHBEND "+this.getPitchBend();break;case g.KEYPRESSURE:d+="KEYPRESS "+this.data[1]}return d};g.prototype.toHexString=function(){for(var d="",c=0;cthis.properties.max_value||this.trigger("on_midi",c)};k.registerNodeType("midi/filter",q);s.title="MIDIEvent";s.desc="Create a MIDI Event";s.prototype.onAction=function(d,c){"assign"==d?(this.properties.channel=c.channel,this.properties.cmd=c.cmd,this.properties.value1= +c.data[1],this.properties.value2=c.data[2]):(c=new g,c.channel=this.properties.channel,this.properties.cmd&&this.properties.cmd.constructor===String?c.setCommandFromString(this.properties.cmd):c.cmd=this.properties.cmd,c.data[0]=c.cmd|c.channel,c.data[1]=Number(this.properties.value1),c.data[2]=Number(this.properties.value2),this.trigger("on_midi",c))};s.prototype.onExecute=function(){var d=this.properties;if(this.outputs)for(var c=0;c=this.size[0]&&(e=this.size[0]-1),a.strokeStyle="red",a.beginPath(),a.moveTo(e,d),a.lineTo(e,0),a.stroke())}};c.title="Visualization";c.desc="Audio Visualization";w.registerNodeType("audio/visualization", +c);a.prototype.onExecute=function(){if(this._freqs=this.getInputData(0)){var a=this.properties.band,b=this.getInputData(1);void 0!==b&&(a=b);b=f.getAudioContext().sampleRate/this._freqs.length;b=a/b*2;b>=this._freqs.length?b=this._freqs[this._freqs.length-1]:(a=b|0,b-=a,b=this._freqs[a]*(1-b)+this._freqs[a+1]*b);this.setOutputData(0,b/255*this.properties.amplitude)}};a.prototype.onGetInputs=function(){return[["band","number"]]};a.title="Signal";a.desc="extract the signal of some frequency";w.registerNodeType("audio/signal", +a);b.prototype.onAdded=function(a){a.status==LGraph.STATUS_RUNNING&&(this.audionode.onaudioprocess=this._callback)};b["@code"]={widget:"code"};b.prototype.onStart=function(){this.audionode.onaudioprocess=this._callback};b.prototype.onStop=function(){this.audionode.onaudioprocess=b._bypass_function};b.prototype.onPause=function(){this.audionode.onaudioprocess=b._bypass_function};b.prototype.onUnpause=function(){this.audionode.onaudioprocess=this._callback};b.prototype.onExecute=function(){};b.prototype.onRemoved= +function(){this.audionode.onaudioprocess=b._bypass_function};b.prototype.processCode=function(){try{this._script=new new Function("properties",this.properties.code)(this.properties),this._old_code=this.properties.code,this._callback=this._script.onaudioprocess}catch(a){console.error("Error in onaudioprocess code",a),this._callback=b._bypass_function,this.audionode.onaudioprocess=this._callback}};b.prototype.onPropertyChanged=function(a,b){"code"==a&&(this.properties.code=b,this.processCode(),this.graph&& +this.graph.status==LGraph.STATUS_RUNNING&&(this.audionode.onaudioprocess=this._callback))};b.default_function=function(){this.onaudioprocess=function(a){var b=a.inputBuffer;a=a.outputBuffer;for(var c=0;c 20) - return; + ctx.strokeStyle = "#333"; + ctx.beginPath(); + ctx.moveTo(0,20); + ctx.lineTo(this.size[0]+1,20); + ctx.moveTo(this.size[0]-20,0); + ctx.lineTo(this.size[0]-25,20); + ctx.moveTo(this.size[0]-40,0); + ctx.lineTo(this.size[0]-45,20); + ctx.stroke(); +} - if( pos[0] > this.size[0] - 20) - this.enabled = !this.enabled; - else if( pos[0] > this.size[0] - 40) - this.visible = !this.visible; - } +TestSpecialNode.prototype.onMouseDown = function(e, pos) +{ + if(pos[1] > 20) + return; - LiteGraph.registerNodeType("test/shape", TestSpecialNode ); \ No newline at end of file + if( pos[0] > this.size[0] - 20) + this.enabled = !this.enabled; + else if( pos[0] > this.size[0] - 40) + this.visible = !this.visible; +} + +LiteGraph.registerNodeType("features/shape", TestSpecialNode ); + + + +//Show value inside the debug console +function TestSlotsNode() +{ + this.addInput("C","number"); + this.addOutput("A","number"); + this.addOutput("B","number"); + this.flags = { horizontal: true }; + this.size = [100,40]; +} + +TestSlotsNode.title = "Flat Slots"; + + +LiteGraph.registerNodeType("features/slots", TestSlotsNode ); diff --git a/doc/classes/ContextMenu.html b/doc/classes/ContextMenu.html index 2680c634a..97b1a3309 100644 --- a/doc/classes/ContextMenu.html +++ b/doc/classes/ContextMenu.html @@ -85,7 +85,7 @@ @@ -123,7 +123,7 @@

Defined in - ../src/litegraph.js:6207 + ../src/litegraph.js:7219

diff --git a/doc/classes/LGraph.html b/doc/classes/LGraph.html index 225af2685..5118294fa 100644 --- a/doc/classes/LGraph.html +++ b/doc/classes/LGraph.html @@ -85,7 +85,7 @@
- Defined in: ../src/litegraph.js:378 + Defined in: ../src/litegraph.js:405
@@ -102,7 +102,13 @@

LGraph

- () +
+ (
    +
  • + o +
  • +
) +
@@ -114,7 +120,7 @@

Defined in - ../src/litegraph.js:378 + ../src/litegraph.js:405

@@ -125,6 +131,23 @@
+
+

Parameters:

+ +
    +
  • + o + Object + + +
    +

    data from previous serialization [optional]

    + +
    + +
  • +
+
@@ -197,6 +220,10 @@
  • findNodesByType +
  • +
  • + getAncestors +
  • getElapsedTime @@ -213,6 +240,10 @@
  • getGlobalOutputData +
  • +
  • + getGroupOnPos +
  • getNodeById @@ -321,7 +352,7 @@

    Defined in - ../src/litegraph.js:856 + ../src/litegraph.js:947

    @@ -381,7 +412,7 @@

    Defined in - ../src/litegraph.js:1065 + ../src/litegraph.js:1194

    @@ -461,7 +492,7 @@

    Defined in - ../src/litegraph.js:1190 + ../src/litegraph.js:1323

    @@ -528,7 +559,7 @@

    Defined in - ../src/litegraph.js:736 + ../src/litegraph.js:827

    @@ -565,7 +596,7 @@

    Defined in - ../src/litegraph.js:453 + ../src/litegraph.js:489

    @@ -621,7 +652,7 @@

    Defined in - ../src/litegraph.js:1150 + ../src/litegraph.js:1281

    @@ -687,7 +718,7 @@

    Defined in - ../src/litegraph.js:1272 + ../src/litegraph.js:1407

    @@ -744,7 +775,7 @@

    Defined in - ../src/litegraph.js:404 + ../src/litegraph.js:435

    @@ -768,6 +799,9 @@
  • str
  • +
  • + returns +
  • )
    @@ -781,7 +815,7 @@

    Defined in - ../src/litegraph.js:1403 + ../src/litegraph.js:1542

    @@ -808,6 +842,17 @@
    +
  • + returns + Boolean + + +
    +

    if there was any error parsing

    + +
    + +
  • @@ -835,7 +880,7 @@

    Defined in - ../src/litegraph.js:472 + ../src/litegraph.js:508

    @@ -891,7 +936,7 @@

    Defined in - ../src/litegraph.js:994 + ../src/litegraph.js:1105

    @@ -957,7 +1002,7 @@

    Defined in - ../src/litegraph.js:1027 + ../src/litegraph.js:1138

    @@ -1023,7 +1068,7 @@

    Defined in - ../src/litegraph.js:1010 + ../src/litegraph.js:1121

    @@ -1064,6 +1109,50 @@
    +
    +
    +

    getAncestors

    + + () + + + Array + + + + + + + + +
    +

    + Defined in + ../src/litegraph.js:790 +

    + + + +
    + +
    +

    Returns all the nodes that could affect this one (ancestors) by crawling all the inputs recursively. +It doesnt include the node itself

    + +
    + + +
    +

    Returns:

    + +
    + Array: +

    an array with all the LGraphNodes that affect this node, in order of execution

    + +
    +
    + +

    getElapsedTime

    @@ -1083,7 +1172,7 @@

    Defined in - ../src/litegraph.js:801 + ../src/litegraph.js:892

    @@ -1127,7 +1216,7 @@ if the nodes are using graphical actions

    Defined in - ../src/litegraph.js:790 + ../src/litegraph.js:881

    @@ -1176,7 +1265,7 @@ if the nodes are using graphical actions

    Defined in - ../src/litegraph.js:1106 + ../src/litegraph.js:1236

    @@ -1241,7 +1330,7 @@ if the nodes are using graphical actions

    Defined in - ../src/litegraph.js:1222 + ../src/litegraph.js:1356

    @@ -1281,6 +1370,86 @@ if the nodes are using graphical actions

    +
    +
    +

    getGroupOnPos

    + +
    + (
      +
    • + x +
    • +
    • + y +
    • +
    ) +
    + + + LGraphGroup + + + + + + + + +
    +

    + Defined in + ../src/litegraph.js:1174 +

    + + + +
    + +
    +

    Returns the top-most group in that position

    + +
    + +
    +

    Parameters:

    + +
      +
    • + x + Number + + +
      +

      the x coordinate in canvas space

      + +
      + +
    • +
    • + y + Number + + +
      +

      the y coordinate in canvas space

      + +
      + +
    • +
    +
    + +
    +

    Returns:

    + +
    + LGraphGroup: +

    the group or null

    + +
    +
    + +

    getNodeById

    @@ -1303,7 +1472,7 @@ if the nodes are using graphical actions

    Defined in - ../src/litegraph.js:981 + ../src/litegraph.js:1092

    @@ -1353,7 +1522,7 @@ if the nodes are using graphical actions

    - Array + LGraphNode @@ -1365,7 +1534,7 @@ if the nodes are using graphical actions

    Defined in - ../src/litegraph.js:1043 + ../src/litegraph.js:1154

    @@ -1421,8 +1590,8 @@ if the nodes are using graphical actions

    Returns:

    - Array: -

    a list with all the nodes that intersect this coordinate

    + LGraphNode: +

    the node at this position or null

    @@ -1453,7 +1622,7 @@ if the nodes are using graphical actions

    Defined in - ../src/litegraph.js:1236 + ../src/litegraph.js:1370

    @@ -1512,7 +1681,7 @@ if the nodes are using graphical actions

    Defined in - ../src/litegraph.js:780 + ../src/litegraph.js:871

    @@ -1552,7 +1721,7 @@ if the nodes are using graphical actions

    Defined in - ../src/litegraph.js:1333 + ../src/litegraph.js:1471

    @@ -1589,7 +1758,7 @@ if the nodes are using graphical actions

    Defined in - ../src/litegraph.js:912 + ../src/litegraph.js:1011

    @@ -1646,7 +1815,7 @@ if the nodes are using graphical actions

    Defined in - ../src/litegraph.js:1169 + ../src/litegraph.js:1301

    @@ -1709,7 +1878,7 @@ if the nodes are using graphical actions

    Defined in - ../src/litegraph.js:1291 + ../src/litegraph.js:1427

    @@ -1765,7 +1934,7 @@ if the nodes are using graphical actions

    Defined in - ../src/litegraph.js:1120 + ../src/litegraph.js:1250

    @@ -1831,7 +2000,7 @@ if the nodes are using graphical actions

    Defined in - ../src/litegraph.js:1245 + ../src/litegraph.js:1379

    @@ -1894,7 +2063,7 @@ if the nodes are using graphical actions

    Defined in - ../src/litegraph.js:540 + ../src/litegraph.js:576

    @@ -1951,7 +2120,7 @@ if the nodes are using graphical actions

    Defined in - ../src/litegraph.js:813 + ../src/litegraph.js:904

    @@ -2013,7 +2182,7 @@ if the nodes are using graphical actions

    Defined in - ../src/litegraph.js:1370 + ../src/litegraph.js:1506

    @@ -2062,7 +2231,7 @@ if the nodes are using graphical actions

    Defined in - ../src/litegraph.js:1083 + ../src/litegraph.js:1213

    @@ -2128,7 +2297,7 @@ if the nodes are using graphical actions

    Defined in - ../src/litegraph.js:1208 + ../src/litegraph.js:1342

    @@ -2194,7 +2363,7 @@ if the nodes are using graphical actions

    Defined in - ../src/litegraph.js:1097 + ../src/litegraph.js:1227

    @@ -2257,7 +2426,7 @@ if the nodes are using graphical actions

    Defined in - ../src/litegraph.js:490 + ../src/litegraph.js:525

    @@ -2305,7 +2474,7 @@ if the nodes are using graphical actions

    Defined in - ../src/litegraph.js:518 + ../src/litegraph.js:554

    @@ -2336,7 +2505,7 @@ if the nodes are using graphical actions

    Defined in - ../src/litegraph.js:619 + ../src/litegraph.js:658

    diff --git a/doc/classes/LGraphCanvas.html b/doc/classes/LGraphCanvas.html index 058a806be..6bf525ced 100644 --- a/doc/classes/LGraphCanvas.html +++ b/doc/classes/LGraphCanvas.html @@ -85,7 +85,7 @@ @@ -126,7 +126,7 @@

    Defined in - ../src/litegraph.js:3325 + ../src/litegraph.js:3624

    @@ -197,6 +197,22 @@

    Methods

    @@ -239,6 +383,130 @@

    Methods

    +
    +

    adjustMouseEvent

    + + () + + + + + + + + +
    +

    + Defined in + ../src/litegraph.js:4730 +

    + + + +
    + +
    +

    adds some useful properties to a mouse event, like the position in graph coordinates

    + +
    + + + + +
    +
    +

    bindEvents

    + + () + + + + + + + + +
    +

    + Defined in + ../src/litegraph.js:3498 +

    + + + +
    + +
    +

    binds mouse, keyboard, touch and drag events to the canvas

    + +
    + + + + +
    +
    +

    bringToFront

    + + () + + + + + + + + +
    +

    + Defined in + ../src/litegraph.js:4816 +

    + + + +
    + +
    +

    brings a node to front (above all other nodes)

    + +
    + + + + +
    +
    +

    centerOnNode

    + + () + + + + + + + + +
    +

    + Defined in + ../src/litegraph.js:4719 +

    + + + +
    + +
    +

    centers the camera on a given node

    + +
    + + + + +

    clear

    @@ -254,7 +522,7 @@

    Defined in - ../src/litegraph.js:2974 + ../src/litegraph.js:3306

    @@ -291,7 +559,7 @@

    Defined in - ../src/litegraph.js:3071 + ../src/litegraph.js:3406

    @@ -323,6 +591,473 @@ +
    +
    +

    computeVisibleNodes

    + + () + + + + + + + + +
    +

    + Defined in + ../src/litegraph.js:4849 +

    + + + +
    + +
    +

    checks which nodes are visible (inside the camera area)

    + +
    + + + + +
    +
    +

    convertCanvasToOffset

    + + () + + + + + + + + +
    +

    + Defined in + ../src/litegraph.js:4798 +

    + + + +
    + +
    +

    converts a coordinate in graphcanvas space to canvas2D space (NAME IS CONFUSION, SHOULD BE THE OTHER WAY AROUND)

    + +
    + + + + +
    +
    +

    convertOffsetToCanvas

    + + () + + + + + + + + +
    +

    + Defined in + ../src/litegraph.js:4786 +

    + + + +
    + +
    +

    converts a coordinate in canvas2D space to graphcanvas space (NAME IS CONFUSION, SHOULD BE THE OTHER WAY AROUND)

    + +
    + + + + +
    +
    +

    deleteSelectedNodes

    + + () + + + + + + + + +
    +

    + Defined in + ../src/litegraph.js:4702 +

    + + + +
    + +
    +

    deletes all nodes in the current selection from the graph

    + +
    + + + + +
    +
    +

    deselectAllNodes

    + + () + + + + + + + + +
    +

    + Defined in + ../src/litegraph.js:4679 +

    + + + +
    + +
    +

    removes all nodes from the current selection

    + +
    + + + + +
    +
    +

    deselectNode

    + + () + + + + + + + + +
    +

    + Defined in + ../src/litegraph.js:4653 +

    + + + +
    + +
    +

    removes a node from the current selection

    + +
    + + + + +
    +
    +

    draw

    + + () + + + + + + + + +
    +

    + Defined in + ../src/litegraph.js:4874 +

    + + + +
    + +
    +

    renders the whole canvas content, by rendering in two separated canvas, one containing the background grid and the connections, and one containing the nodes)

    + +
    + + + + +
    +
    +

    drawBackCanvas

    + + () + + + + + + + + +
    +

    + Defined in + ../src/litegraph.js:5064 +

    + + + +
    + +
    +

    draws the back canvas (the one containing the background and the connections)

    + +
    + + + + +
    +
    +

    drawConnections

    + + () + + + + + + + + +
    +

    + Defined in + ../src/litegraph.js:5639 +

    + + + +
    + +
    +

    draws every connection visible in the canvas +OPTIMIZE THIS: precatch connections position instead of recomputing them every time

    + +
    + + + + +
    +
    +

    drawFrontCanvas

    + + () + + + + + + + + +
    +

    + Defined in + ../src/litegraph.js:4905 +

    + + + +
    + +
    +

    draws the front canvas (the one containing all the nodes)

    + +
    + + + + +
    +
    +

    drawGroups

    + + () + + + + + + + + +
    +

    + Defined in + ../src/litegraph.js:5990 +

    + + + +
    + +
    +

    draws every group area in the background

    + +
    + + + + +
    +
    +

    drawNode

    + + () + + + + + + + + +
    +

    + Defined in + ../src/litegraph.js:5196 +

    + + + +
    + +
    +

    draws the given node inside the canvas

    + +
    + + + + +
    +
    +

    drawNodeShape

    + + () + + + + + + + + +
    +

    + Defined in + ../src/litegraph.js:5467 +

    + + + +
    + +
    +

    draws the shape of the given node in the canvas

    + +
    + + + + +
    +
    +

    drawNodeWidgets

    + + () + + + + + + + + +
    +

    + Defined in + ../src/litegraph.js:5815 +

    + + + +
    + +
    +

    draws the widgets stored inside a node

    + +
    + + + + +
    +
    +

    enableWebGL

    + + () + + + + + + + + +
    +

    + Defined in + ../src/litegraph.js:3598 +

    + + + +
    + +
    +

    this function allows to render the canvas using WebGL instead of Canvas2D +this is useful if you plant to render 3D objects inside your nodes, it uses litegl.js for webgl and canvas2DtoWebGL to emulate the Canvas2D calls in webGL

    + +
    + + + +

    getCanvasWindow

    @@ -342,7 +1077,7 @@

    Defined in - ../src/litegraph.js:3341 + ../src/litegraph.js:3640

    @@ -366,6 +1101,68 @@
    +
    +
    +

    isOverNodeBox

    + + () + + + + + + + + +
    +

    + Defined in + ../src/litegraph.js:4296 +

    + + + +
    + +
    +

    retuns true if a position (in graph space) is on top of a node little corner box

    + +
    + + + + +
    +
    +

    isOverNodeInput

    + + () + + + + + + + + +
    +

    + Defined in + ../src/litegraph.js:4308 +

    + + + +
    + +
    +

    retuns true if a position (in graph space) is on top of a node input slot

    + +
    + + + +

    openSubgraph

    @@ -388,7 +1185,7 @@

    Defined in - ../src/litegraph.js:3044 + ../src/litegraph.js:3379

    @@ -419,6 +1216,378 @@ +
    +
    +

    processDrop

    + + () + + + + + + + + +
    +

    + Defined in + ../src/litegraph.js:4485 +

    + + + +
    + +
    +

    process a item drop event on top the canvas

    + +
    + + + + +
    +
    +

    processKey

    + + () + + + + + + + + +
    +

    + Defined in + ../src/litegraph.js:4332 +

    + + + +
    + +
    +

    process a key event

    + +
    + + + + +
    +
    +

    processMouseMove

    + + () + + + + + + + + +
    +

    + Defined in + ../src/litegraph.js:3921 +

    + + + +
    + +
    +

    Called when a mouse move event has to be processed

    + +
    + + + + +
    +
    +

    processMouseUp

    + + () + + + + + + + + +
    +

    + Defined in + ../src/litegraph.js:4112 +

    + + + +
    + +
    +

    Called when a mouse up event has to be processed

    + +
    + + + + +
    +
    +

    processMouseWheel

    + + () + + + + + + + + +
    +

    + Defined in + ../src/litegraph.js:4263 +

    + + + +
    + +
    +

    Called when a mouse wheel event has to be processed

    + +
    + + + + +
    +
    +

    processNodeWidgets

    + + () + + + + + + + + +
    +

    + Defined in + ../src/litegraph.js:5911 +

    + + + +
    + +
    +

    process an event on widgets

    + +
    + + + + +
    +
    +

    renderInfo

    + + () + + + + + + + + +
    +

    + Defined in + ../src/litegraph.js:5038 +

    + + + +
    + +
    +

    draws some useful stats in the corner of the canvas

    + +
    + + + + +
    + +
    +

    resize

    + + () + + + + + + + + +
    +

    + Defined in + ../src/litegraph.js:6035 +

    + + + +
    + +
    +

    resizes the canvas to a given size, if no size is passed, then it tries to fill the parentNode

    + +
    + + + + +
    +
    +

    selectNode

    + + () + + + + + + + + +
    +

    + Defined in + ../src/litegraph.js:4603 +

    + + + +
    + +
    +

    selects a given node (or adds it to the current selection)

    + +
    + + + + +
    +
    +

    selectNodes

    + + () + + + + + + + + +
    +

    + Defined in + ../src/litegraph.js:4615 +

    + + + +
    + +
    +

    selects several nodes (or adds them to the current selection)

    + +
    + + + + +
    +
    +

    sendToBack

    + + () + + + + + + + + +
    +

    + Defined in + ../src/litegraph.js:4829 +

    + + + +
    + +
    +

    sends a node to the back (below all other nodes)

    + +
    + + + +

    setCanvas

    @@ -441,7 +1610,7 @@

    Defined in - ../src/litegraph.js:3088 + ../src/litegraph.js:3423

    @@ -495,7 +1664,7 @@

    Defined in - ../src/litegraph.js:3013 + ../src/litegraph.js:3348

    @@ -526,6 +1695,37 @@ +
    +
    +

    setZoom

    + + () + + + + + + + + +
    +

    + Defined in + ../src/litegraph.js:4758 +

    + + + +
    + +
    +

    changes the zoom level of the graph (default is 1), you can pass also a place used to pivot the zoom

    + +
    + + + +

    startRendering

    @@ -542,7 +1742,7 @@

    Defined in - ../src/litegraph.js:3355 + ../src/litegraph.js:3654

    @@ -573,7 +1773,7 @@

    Defined in - ../src/litegraph.js:3379 + ../src/litegraph.js:3678

    @@ -588,6 +1788,69 @@ +
    +
    +

    switchLiveMode

    + + () + + + + + + + + +
    +

    + Defined in + ../src/litegraph.js:6058 +

    + + + +
    + +
    +

    switches to live mode (node shapes are not rendered, only the content) +this feature was designed when graphs where meant to create user interfaces

    + +
    + + + + +
    +
    +

    unbindEvents

    + + () + + + + + + + + +
    +

    + Defined in + ../src/litegraph.js:3550 +

    + + + +
    + +
    +

    unbinds mouse events from the canvas

    + +
    + + + +
    diff --git a/doc/classes/LGraphNode.html b/doc/classes/LGraphNode.html index ca2e83908..f8c018b31 100644 --- a/doc/classes/LGraphNode.html +++ b/doc/classes/LGraphNode.html @@ -85,7 +85,7 @@ @@ -136,6 +136,10 @@
  • addProperty +
  • +
  • + addWidget +
  • collapse @@ -230,7 +234,7 @@
  • - isPointInsideNode + isPointInside
  • @@ -305,7 +309,7 @@

    Defined in - ../src/litegraph.js:2276 + ../src/litegraph.js:2434

    @@ -397,7 +401,7 @@

    Defined in - ../src/litegraph.js:2213 + ../src/litegraph.js:2371

    @@ -472,7 +476,7 @@

    Defined in - ../src/litegraph.js:2237 + ../src/litegraph.js:2395

    @@ -532,7 +536,7 @@

    Defined in - ../src/litegraph.js:2151 + ../src/litegraph.js:2309

    @@ -607,7 +611,7 @@

    Defined in - ../src/litegraph.js:2174 + ../src/litegraph.js:2332

    @@ -670,7 +674,7 @@

    Defined in - ../src/litegraph.js:2125 + ../src/litegraph.js:2283

    @@ -733,6 +737,49 @@ +
    +
    +

    addWidget

    + + () + + + Float32Array4 + + + + + + + + +
    +

    + Defined in + ../src/litegraph.js:2510 +

    + + + +
    + +
    +

    Allows to pass

    + +
    + + +
    +

    Returns:

    + +
    + Float32Array4: +

    the total size

    + +
    +
    + +

    collapse

    @@ -749,7 +796,7 @@

    Defined in - ../src/litegraph.js:2860 + ../src/litegraph.js:3072

    @@ -789,7 +836,7 @@

    Defined in - ../src/litegraph.js:2297 + ../src/litegraph.js:2455

    @@ -845,7 +892,7 @@

    Defined in - ../src/litegraph.js:1590 + ../src/litegraph.js:1744

    @@ -891,7 +938,7 @@

    Defined in - ../src/litegraph.js:2450 + ../src/litegraph.js:2640

    @@ -979,7 +1026,7 @@

    Defined in - ../src/litegraph.js:2658 + ../src/litegraph.js:2870

    @@ -1048,7 +1095,7 @@

    Defined in - ../src/litegraph.js:2568 + ../src/litegraph.js:2768

    @@ -1125,7 +1172,7 @@

    Defined in - ../src/litegraph.js:2419 + ../src/litegraph.js:2609

    @@ -1191,7 +1238,7 @@

    Defined in - ../src/litegraph.js:2435 + ../src/litegraph.js:2625

    @@ -1251,7 +1298,7 @@

    Defined in - ../src/litegraph.js:2348 + ../src/litegraph.js:2538

    @@ -1304,7 +1351,7 @@ bounding is: [topleft_cornerx, topleft_cornery, width, height]

    Defined in - ../src/litegraph.js:2726 + ../src/litegraph.js:2939

    @@ -1384,7 +1431,7 @@ bounding is: [topleft_cornerx, topleft_cornery, width, height]

    Defined in - ../src/litegraph.js:1833 + ../src/litegraph.js:1991

    @@ -1463,7 +1510,7 @@ bounding is: [topleft_cornerx, topleft_cornery, width, height]

    Defined in - ../src/litegraph.js:1869 + ../src/litegraph.js:2027

    @@ -1539,7 +1586,7 @@ bounding is: [topleft_cornerx, topleft_cornery, width, height]

    Defined in - ../src/litegraph.js:1898 + ../src/litegraph.js:2056

    @@ -1604,7 +1651,7 @@ bounding is: [topleft_cornerx, topleft_cornery, width, height]

    Defined in - ../src/litegraph.js:1913 + ../src/litegraph.js:2071

    @@ -1669,7 +1716,7 @@ bounding is: [topleft_cornerx, topleft_cornery, width, height]

    Defined in - ../src/litegraph.js:1935 + ../src/litegraph.js:2093

    @@ -1734,7 +1781,7 @@ bounding is: [topleft_cornerx, topleft_cornery, width, height]

    Defined in - ../src/litegraph.js:1959 + ../src/litegraph.js:2117

    @@ -1799,7 +1846,7 @@ bounding is: [topleft_cornerx, topleft_cornery, width, height]

    Defined in - ../src/litegraph.js:1977 + ../src/litegraph.js:2135

    @@ -1864,7 +1911,7 @@ bounding is: [topleft_cornerx, topleft_cornery, width, height]

    Defined in - ../src/litegraph.js:2022 + ../src/litegraph.js:2180

    @@ -1930,7 +1977,7 @@ bounding is: [topleft_cornerx, topleft_cornery, width, height]

    Defined in - ../src/litegraph.js:2388 + ../src/litegraph.js:2578

    @@ -1996,7 +2043,7 @@ bounding is: [topleft_cornerx, topleft_cornery, width, height]

    Defined in - ../src/litegraph.js:1784 + ../src/litegraph.js:1942

    @@ -2030,7 +2077,7 @@ bounding is: [topleft_cornerx, topleft_cornery, width, height]

    Defined in - ../src/litegraph.js:2006 + ../src/litegraph.js:2164

    @@ -2077,7 +2124,7 @@ bounding is: [topleft_cornerx, topleft_cornery, width, height]

    Defined in - ../src/litegraph.js:1885 + ../src/litegraph.js:2043

    @@ -2140,7 +2187,7 @@ bounding is: [topleft_cornerx, topleft_cornery, width, height]

    Defined in - ../src/litegraph.js:1993 + ../src/litegraph.js:2151

    @@ -2179,8 +2226,8 @@ bounding is: [topleft_cornerx, topleft_cornery, width, height]

    -
    -

    isPointInsideNode

    +
    +

    isPointInside

    (
      @@ -2206,7 +2253,7 @@ bounding is: [topleft_cornerx, topleft_cornery, width, height]

      Defined in - ../src/litegraph.js:2364 + ../src/litegraph.js:2554

      @@ -2270,7 +2317,7 @@ bounding is: [topleft_cornerx, topleft_cornery, width, height]

      Defined in - ../src/litegraph.js:2873 + ../src/litegraph.js:3088

      @@ -2307,7 +2354,7 @@ bounding is: [topleft_cornerx, topleft_cornery, width, height]

      Defined in - ../src/litegraph.js:2262 + ../src/litegraph.js:2420

      @@ -2360,7 +2407,7 @@ bounding is: [topleft_cornerx, topleft_cornery, width, height]

      Defined in - ../src/litegraph.js:2199 + ../src/litegraph.js:2357

      @@ -2407,7 +2454,7 @@ bounding is: [topleft_cornerx, topleft_cornery, width, height]

      Defined in - ../src/litegraph.js:1690 + ../src/litegraph.js:1848

      @@ -2447,7 +2494,7 @@ bounding is: [topleft_cornerx, topleft_cornery, width, height]

      Defined in - ../src/litegraph.js:1797 + ../src/litegraph.js:1955

      @@ -2504,7 +2551,7 @@ bounding is: [topleft_cornerx, topleft_cornery, width, height]

      Defined in - ../src/litegraph.js:1772 + ../src/litegraph.js:1930

      @@ -2544,7 +2591,7 @@ bounding is: [topleft_cornerx, topleft_cornery, width, height]

      Defined in - ../src/litegraph.js:2055 + ../src/litegraph.js:2213

      @@ -2611,7 +2658,7 @@ bounding is: [topleft_cornerx, topleft_cornery, width, height]

      Defined in - ../src/litegraph.js:2078 + ../src/litegraph.js:2236

      diff --git a/doc/classes/LiteGraph.html b/doc/classes/LiteGraph.html index 5107d175d..5f5241314 100644 --- a/doc/classes/LiteGraph.html +++ b/doc/classes/LiteGraph.html @@ -205,7 +205,7 @@

      Defined in - ../src/litegraph.js:165 + ../src/litegraph.js:179

      @@ -265,7 +265,7 @@

      Defined in - ../src/litegraph.js:183 + ../src/litegraph.js:197

      @@ -344,7 +344,7 @@

      Defined in - ../src/litegraph.js:226 + ../src/litegraph.js:240

      @@ -410,7 +410,7 @@

      Defined in - ../src/litegraph.js:239 + ../src/litegraph.js:253

      @@ -470,7 +470,7 @@

      Defined in - ../src/litegraph.js:261 + ../src/litegraph.js:281

      @@ -519,7 +519,7 @@

      Defined in - ../src/litegraph.js:70 + ../src/litegraph.js:82

      @@ -593,7 +593,7 @@

      Defined in - ../src/litegraph.js:135 + ../src/litegraph.js:149

      diff --git a/doc/data.json b/doc/data.json index 1c97a5a45..bf60ccdd5 100644 --- a/doc/data.json +++ b/doc/data.json @@ -39,9 +39,16 @@ "plugin_for": [], "extension_for": [], "file": "../src/litegraph.js", - "line": 378, + "line": 405, "description": "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.", - "is_constructor": 1 + "is_constructor": 1, + "params": [ + { + "name": "o", + "description": "data from previous serialization [optional]", + "type": "Object" + } + ] }, "LGraphNode": { "name": "LGraphNode", @@ -52,7 +59,7 @@ "plugin_for": [], "extension_for": [], "file": "../src/litegraph.js", - "line": 1535, + "line": 1692, "description": "Base Class for all the node type classes", "params": [ { @@ -71,7 +78,7 @@ "plugin_for": [], "extension_for": [], "file": "../src/litegraph.js", - "line": 3325, + "line": 3624, "description": "marks as dirty the canvas, this way it will be rendered again", "is_constructor": 1, "params": [ @@ -102,7 +109,7 @@ "plugin_for": [], "extension_for": [], "file": "../src/litegraph.js", - "line": 6207, + "line": 7219, "description": "ContextMenu from LiteGUI", "is_constructor": 1, "params": [ @@ -123,7 +130,7 @@ "classitems": [ { "file": "../src/litegraph.js", - "line": 70, + "line": 82, "description": "Register a node class so it can be listed when the user wants to create a new one", "itemtype": "method", "name": "registerNodeType", @@ -143,7 +150,7 @@ }, { "file": "../src/litegraph.js", - "line": 135, + "line": 149, "description": "Create a new node type by passing a function, it wraps it with a propper class and generates inputs according to the parameters of the function.\nUseful to wrap simple methods that do not require properties, and that only process some input to generate an output.", "itemtype": "method", "name": "wrapFunctionAsNode", @@ -173,7 +180,7 @@ }, { "file": "../src/litegraph.js", - "line": 165, + "line": 179, "description": "Adds this method to all nodetypes, existing and to be created\n(You can add it to LGraphNode.prototype but then existing node types wont have it)", "itemtype": "method", "name": "addNodeMethod", @@ -188,7 +195,7 @@ }, { "file": "../src/litegraph.js", - "line": 183, + "line": 197, "description": "Create a node of a given type with a name. The node is not attached to any graph yet.", "itemtype": "method", "name": "createNode", @@ -213,7 +220,7 @@ }, { "file": "../src/litegraph.js", - "line": 226, + "line": 240, "description": "Returns a registered node type with a given name", "itemtype": "method", "name": "getNodeType", @@ -232,7 +239,7 @@ }, { "file": "../src/litegraph.js", - "line": 239, + "line": 253, "description": "Returns a list of node types matching one category", "itemtype": "method", "name": "getNodeType", @@ -251,7 +258,7 @@ }, { "file": "../src/litegraph.js", - "line": 261, + "line": 281, "description": "Returns a list with all the node type categories", "itemtype": "method", "name": "getNodeTypesCategories", @@ -263,7 +270,7 @@ }, { "file": "../src/litegraph.js", - "line": 404, + "line": 435, "description": "Removes all nodes from this graph", "itemtype": "method", "name": "clear", @@ -271,7 +278,7 @@ }, { "file": "../src/litegraph.js", - "line": 453, + "line": 489, "description": "Attach Canvas to this graph", "itemtype": "method", "name": "attachCanvas", @@ -286,7 +293,7 @@ }, { "file": "../src/litegraph.js", - "line": 472, + "line": 508, "description": "Detach Canvas from this graph", "itemtype": "method", "name": "detachCanvas", @@ -301,7 +308,7 @@ }, { "file": "../src/litegraph.js", - "line": 490, + "line": 525, "description": "Starts running this graph every interval milliseconds.", "itemtype": "method", "name": "start", @@ -316,7 +323,7 @@ }, { "file": "../src/litegraph.js", - "line": 518, + "line": 554, "description": "Stops the execution loop of the graph", "itemtype": "method", "name": "stop execution", @@ -324,7 +331,7 @@ }, { "file": "../src/litegraph.js", - "line": 540, + "line": 576, "description": "Run N steps (cycles) of the graph", "itemtype": "method", "name": "runStep", @@ -339,7 +346,7 @@ }, { "file": "../src/litegraph.js", - "line": 619, + "line": 658, "description": "Updates the graph execution order according to relevance of the nodes (nodes with only outputs have more relevance than\nnodes with only inputs.", "itemtype": "method", "name": "updateExecutionOrder", @@ -347,7 +354,19 @@ }, { "file": "../src/litegraph.js", - "line": 736, + "line": 790, + "description": "Returns all the nodes that could affect this one (ancestors) by crawling all the inputs recursively.\nIt doesnt include the node itself", + "itemtype": "method", + "name": "getAncestors", + "return": { + "description": "an array with all the LGraphNodes that affect this node, in order of execution", + "type": "Array" + }, + "class": "LGraph" + }, + { + "file": "../src/litegraph.js", + "line": 827, "description": "Positions every node in a more readable manner", "itemtype": "method", "name": "arrange", @@ -355,7 +374,7 @@ }, { "file": "../src/litegraph.js", - "line": 780, + "line": 871, "description": "Returns the amount of time the graph has been running in milliseconds", "itemtype": "method", "name": "getTime", @@ -367,7 +386,7 @@ }, { "file": "../src/litegraph.js", - "line": 790, + "line": 881, "description": "Returns the amount of time accumulated using the fixedtime_lapse var. This is used in context where the time increments should be constant", "itemtype": "method", "name": "getFixedTime", @@ -379,7 +398,7 @@ }, { "file": "../src/litegraph.js", - "line": 801, + "line": 892, "description": "Returns the amount of time it took to compute the latest iteration. Take into account that this number could be not correct\nif the nodes are using graphical actions", "itemtype": "method", "name": "getElapsedTime", @@ -391,7 +410,7 @@ }, { "file": "../src/litegraph.js", - "line": 813, + "line": 904, "description": "Sends an event to all the nodes, useful to trigger stuff", "itemtype": "method", "name": "sendEventToAllNodes", @@ -411,7 +430,7 @@ }, { "file": "../src/litegraph.js", - "line": 856, + "line": 947, "description": "Adds a new node instasnce to this graph", "itemtype": "method", "name": "add", @@ -426,7 +445,7 @@ }, { "file": "../src/litegraph.js", - "line": 912, + "line": 1011, "description": "Removes a node from the graph", "itemtype": "method", "name": "remove", @@ -441,7 +460,7 @@ }, { "file": "../src/litegraph.js", - "line": 981, + "line": 1092, "description": "Returns a node by its id.", "itemtype": "method", "name": "getNodeById", @@ -456,7 +475,7 @@ }, { "file": "../src/litegraph.js", - "line": 994, + "line": 1105, "description": "Returns a list of nodes that matches a class", "itemtype": "method", "name": "findNodesByClass", @@ -475,7 +494,7 @@ }, { "file": "../src/litegraph.js", - "line": 1010, + "line": 1121, "description": "Returns a list of nodes that matches a type", "itemtype": "method", "name": "findNodesByType", @@ -494,7 +513,7 @@ }, { "file": "../src/litegraph.js", - "line": 1027, + "line": 1138, "description": "Returns a list of nodes that matches a name", "itemtype": "method", "name": "findNodesByTitle", @@ -513,7 +532,7 @@ }, { "file": "../src/litegraph.js", - "line": 1043, + "line": 1154, "description": "Returns the top-most node in this position of the canvas", "itemtype": "method", "name": "getNodeOnPos", @@ -535,14 +554,38 @@ } ], "return": { - "description": "a list with all the nodes that intersect this coordinate", - "type": "Array" + "description": "the node at this position or null", + "type": "LGraphNode" }, "class": "LGraph" }, { "file": "../src/litegraph.js", - "line": 1065, + "line": 1174, + "description": "Returns the top-most group in that position", + "itemtype": "method", + "name": "getGroupOnPos", + "params": [ + { + "name": "x", + "description": "the x coordinate in canvas space", + "type": "Number" + }, + { + "name": "y", + "description": "the y coordinate in canvas space", + "type": "Number" + } + ], + "return": { + "description": "the group or null", + "type": "LGraphGroup" + }, + "class": "LGraph" + }, + { + "file": "../src/litegraph.js", + "line": 1194, "description": "Tell this graph it has a global graph input of this type", "itemtype": "method", "name": "addGlobalInput", @@ -567,7 +610,7 @@ }, { "file": "../src/litegraph.js", - "line": 1083, + "line": 1213, "description": "Assign a data to the global graph input", "itemtype": "method", "name": "setGlobalInputData", @@ -587,7 +630,7 @@ }, { "file": "../src/litegraph.js", - "line": 1097, + "line": 1227, "description": "Assign a data to the global graph input (same as setGlobalInputData)", "itemtype": "method", "name": "setInputData", @@ -607,7 +650,7 @@ }, { "file": "../src/litegraph.js", - "line": 1106, + "line": 1236, "description": "Returns the current value of a global graph input", "itemtype": "method", "name": "getGlobalInputData", @@ -626,7 +669,7 @@ }, { "file": "../src/litegraph.js", - "line": 1120, + "line": 1250, "description": "Changes the name of a global graph input", "itemtype": "method", "name": "renameGlobalInput", @@ -646,7 +689,7 @@ }, { "file": "../src/litegraph.js", - "line": 1150, + "line": 1281, "description": "Changes the type of a global graph input", "itemtype": "method", "name": "changeGlobalInputType", @@ -666,7 +709,7 @@ }, { "file": "../src/litegraph.js", - "line": 1169, + "line": 1301, "description": "Removes a global graph input", "itemtype": "method", "name": "removeGlobalInput", @@ -686,7 +729,7 @@ }, { "file": "../src/litegraph.js", - "line": 1190, + "line": 1323, "description": "Creates a global graph output", "itemtype": "method", "name": "addGlobalOutput", @@ -711,7 +754,7 @@ }, { "file": "../src/litegraph.js", - "line": 1208, + "line": 1342, "description": "Assign a data to the global output", "itemtype": "method", "name": "setGlobalOutputData", @@ -731,7 +774,7 @@ }, { "file": "../src/litegraph.js", - "line": 1222, + "line": 1356, "description": "Returns the current value of a global graph output", "itemtype": "method", "name": "getGlobalOutputData", @@ -750,7 +793,7 @@ }, { "file": "../src/litegraph.js", - "line": 1236, + "line": 1370, "description": "Returns the current value of a global graph output (sames as getGlobalOutputData)", "itemtype": "method", "name": "getOutputData", @@ -769,7 +812,7 @@ }, { "file": "../src/litegraph.js", - "line": 1245, + "line": 1379, "description": "Renames a global graph output", "itemtype": "method", "name": "renameGlobalOutput", @@ -789,7 +832,7 @@ }, { "file": "../src/litegraph.js", - "line": 1272, + "line": 1407, "description": "Changes the type of a global graph output", "itemtype": "method", "name": "changeGlobalOutputType", @@ -809,7 +852,7 @@ }, { "file": "../src/litegraph.js", - "line": 1291, + "line": 1427, "description": "Removes a global graph output", "itemtype": "method", "name": "removeGlobalOutput", @@ -824,7 +867,7 @@ }, { "file": "../src/litegraph.js", - "line": 1333, + "line": 1471, "description": "returns if the graph is in live mode", "itemtype": "method", "name": "isLive", @@ -832,7 +875,7 @@ }, { "file": "../src/litegraph.js", - "line": 1370, + "line": 1506, "description": "Creates a Object containing all the info about this graph, it can be serialized", "itemtype": "method", "name": "serialize", @@ -844,7 +887,7 @@ }, { "file": "../src/litegraph.js", - "line": 1403, + "line": 1542, "description": "Configure a graph from a JSON string", "itemtype": "method", "name": "configure", @@ -853,13 +896,18 @@ "name": "str", "description": "configure a graph from a JSON string", "type": "String" + }, + { + "name": "returns", + "description": "if there was any error parsing", + "type": "Boolean" } ], "class": "LGraph" }, { "file": "../src/litegraph.js", - "line": 1590, + "line": 1744, "description": "configure a node from an object containing the serialized info", "itemtype": "method", "name": "configure", @@ -867,7 +915,7 @@ }, { "file": "../src/litegraph.js", - "line": 1690, + "line": 1848, "description": "serialize the content", "itemtype": "method", "name": "serialize", @@ -875,7 +923,7 @@ }, { "file": "../src/litegraph.js", - "line": 1772, + "line": 1930, "description": "serialize and stringify", "itemtype": "method", "name": "toString", @@ -883,7 +931,7 @@ }, { "file": "../src/litegraph.js", - "line": 1784, + "line": 1942, "description": "get the title string", "itemtype": "method", "name": "getTitle", @@ -891,7 +939,7 @@ }, { "file": "../src/litegraph.js", - "line": 1797, + "line": 1955, "description": "sets the output data", "itemtype": "method", "name": "setOutputData", @@ -911,7 +959,7 @@ }, { "file": "../src/litegraph.js", - "line": 1833, + "line": 1991, "description": "Retrieves the input data (data traveling through the connection) from one slot", "itemtype": "method", "name": "getInputData", @@ -935,7 +983,7 @@ }, { "file": "../src/litegraph.js", - "line": 1869, + "line": 2027, "description": "Retrieves the input data from one slot using its name instead of slot number", "itemtype": "method", "name": "getInputDataByName", @@ -959,7 +1007,7 @@ }, { "file": "../src/litegraph.js", - "line": 1885, + "line": 2043, "description": "tells you if there is a connection in one input slot", "itemtype": "method", "name": "isInputConnected", @@ -978,7 +1026,7 @@ }, { "file": "../src/litegraph.js", - "line": 1898, + "line": 2056, "description": "tells you info about an input connection (which node, type, etc)", "itemtype": "method", "name": "getInputInfo", @@ -997,7 +1045,7 @@ }, { "file": "../src/litegraph.js", - "line": 1913, + "line": 2071, "description": "returns the node connected in the input slot", "itemtype": "method", "name": "getInputNode", @@ -1016,7 +1064,7 @@ }, { "file": "../src/litegraph.js", - "line": 1935, + "line": 2093, "description": "returns the value of an input with this name, otherwise checks if there is a property with that name", "itemtype": "method", "name": "getInputOrProperty", @@ -1035,7 +1083,7 @@ }, { "file": "../src/litegraph.js", - "line": 1959, + "line": 2117, "description": "tells you the last output data that went in that slot", "itemtype": "method", "name": "getOutputData", @@ -1054,7 +1102,7 @@ }, { "file": "../src/litegraph.js", - "line": 1977, + "line": 2135, "description": "tells you info about an output connection (which node, type, etc)", "itemtype": "method", "name": "getOutputInfo", @@ -1073,7 +1121,7 @@ }, { "file": "../src/litegraph.js", - "line": 1993, + "line": 2151, "description": "tells you if there is a connection in one output slot", "itemtype": "method", "name": "isOutputConnected", @@ -1092,7 +1140,7 @@ }, { "file": "../src/litegraph.js", - "line": 2006, + "line": 2164, "description": "tells you if there is any connection in the output slots", "itemtype": "method", "name": "isAnyOutputConnected", @@ -1104,7 +1152,7 @@ }, { "file": "../src/litegraph.js", - "line": 2022, + "line": 2180, "description": "retrieves all the nodes connected to this output slot", "itemtype": "method", "name": "getOutputNodes", @@ -1123,7 +1171,7 @@ }, { "file": "../src/litegraph.js", - "line": 2055, + "line": 2213, "description": "Triggers an event in this node, this will trigger any output with the same name", "itemtype": "method", "name": "trigger", @@ -1143,7 +1191,7 @@ }, { "file": "../src/litegraph.js", - "line": 2078, + "line": 2236, "description": "Triggers an slot event in this node", "itemtype": "method", "name": "triggerSlot", @@ -1163,7 +1211,7 @@ }, { "file": "../src/litegraph.js", - "line": 2125, + "line": 2283, "description": "add a new property to this node", "itemtype": "method", "name": "addProperty", @@ -1193,7 +1241,7 @@ }, { "file": "../src/litegraph.js", - "line": 2151, + "line": 2309, "description": "add a new output slot to use in this node", "itemtype": "method", "name": "addOutput", @@ -1218,7 +1266,7 @@ }, { "file": "../src/litegraph.js", - "line": 2174, + "line": 2332, "description": "add a new output slot to use in this node", "itemtype": "method", "name": "addOutputs", @@ -1233,7 +1281,7 @@ }, { "file": "../src/litegraph.js", - "line": 2199, + "line": 2357, "description": "remove an existing output slot", "itemtype": "method", "name": "removeOutput", @@ -1248,7 +1296,7 @@ }, { "file": "../src/litegraph.js", - "line": 2213, + "line": 2371, "description": "add a new input slot to use in this node", "itemtype": "method", "name": "addInput", @@ -1273,7 +1321,7 @@ }, { "file": "../src/litegraph.js", - "line": 2237, + "line": 2395, "description": "add several new input slots in this node", "itemtype": "method", "name": "addInputs", @@ -1288,7 +1336,7 @@ }, { "file": "../src/litegraph.js", - "line": 2262, + "line": 2420, "description": "remove an existing input slot", "itemtype": "method", "name": "removeInput", @@ -1303,7 +1351,7 @@ }, { "file": "../src/litegraph.js", - "line": 2276, + "line": 2434, "description": "add an special connection to this node (used for special kinds of graphs)", "itemtype": "method", "name": "addConnection", @@ -1333,7 +1381,7 @@ }, { "file": "../src/litegraph.js", - "line": 2297, + "line": 2455, "description": "computes the size of a node according to its inputs and output slots", "itemtype": "method", "name": "computeSize", @@ -1352,7 +1400,19 @@ }, { "file": "../src/litegraph.js", - "line": 2348, + "line": 2510, + "description": "Allows to pass", + "itemtype": "method", + "name": "addWidget", + "return": { + "description": "the total size", + "type": "Float32Array[4]" + }, + "class": "LGraphNode" + }, + { + "file": "../src/litegraph.js", + "line": 2538, "description": "returns the bounding of the object, used for rendering purposes\nbounding is: [topleft_cornerx, topleft_cornery, width, height]", "itemtype": "method", "name": "getBounding", @@ -1364,10 +1424,10 @@ }, { "file": "../src/litegraph.js", - "line": 2364, + "line": 2554, "description": "checks if a point is inside the shape of a node", "itemtype": "method", - "name": "isPointInsideNode", + "name": "isPointInside", "params": [ { "name": "x", @@ -1388,7 +1448,7 @@ }, { "file": "../src/litegraph.js", - "line": 2388, + "line": 2578, "description": "checks if a point is inside a node slot, and returns info about which slot", "itemtype": "method", "name": "getSlotInPosition", @@ -1412,7 +1472,7 @@ }, { "file": "../src/litegraph.js", - "line": 2419, + "line": 2609, "description": "returns the input slot with a given name (used for dynamic slots), -1 if not found", "itemtype": "method", "name": "findInputSlot", @@ -1431,7 +1491,7 @@ }, { "file": "../src/litegraph.js", - "line": 2435, + "line": 2625, "description": "returns the output slot with a given name (used for dynamic slots), -1 if not found", "itemtype": "method", "name": "findOutputSlot", @@ -1450,7 +1510,7 @@ }, { "file": "../src/litegraph.js", - "line": 2450, + "line": 2640, "description": "connect this node output to the input of another node", "itemtype": "method", "name": "connect", @@ -1479,7 +1539,7 @@ }, { "file": "../src/litegraph.js", - "line": 2568, + "line": 2768, "description": "disconnect one output to an specific node", "itemtype": "method", "name": "disconnectOutput", @@ -1503,7 +1563,7 @@ }, { "file": "../src/litegraph.js", - "line": 2658, + "line": 2870, "description": "disconnect one input", "itemtype": "method", "name": "disconnectInput", @@ -1522,7 +1582,7 @@ }, { "file": "../src/litegraph.js", - "line": 2726, + "line": 2939, "description": "returns the center of a connection point in canvas coords", "itemtype": "method", "name": "getConnectionPos", @@ -1546,7 +1606,7 @@ }, { "file": "../src/litegraph.js", - "line": 2860, + "line": 3072, "description": "Collapse the node to make it smaller on the canvas", "itemtype": "method", "name": "collapse", @@ -1554,7 +1614,7 @@ }, { "file": "../src/litegraph.js", - "line": 2873, + "line": 3088, "description": "Forces the node to do not move or realign on Z", "itemtype": "method", "name": "pin", @@ -1562,7 +1622,7 @@ }, { "file": "../src/litegraph.js", - "line": 2974, + "line": 3306, "description": "clears all the data inside", "itemtype": "method", "name": "clear", @@ -1570,7 +1630,7 @@ }, { "file": "../src/litegraph.js", - "line": 3013, + "line": 3348, "description": "assigns a graph, you can reasign graphs to the same canvas", "itemtype": "method", "name": "setGraph", @@ -1585,7 +1645,7 @@ }, { "file": "../src/litegraph.js", - "line": 3044, + "line": 3379, "description": "opens a graph contained inside a node in the current graph", "itemtype": "method", "name": "openSubgraph", @@ -1600,7 +1660,7 @@ }, { "file": "../src/litegraph.js", - "line": 3071, + "line": 3406, "description": "closes a subgraph contained inside a node", "itemtype": "method", "name": "closeSubgraph", @@ -1615,7 +1675,7 @@ }, { "file": "../src/litegraph.js", - "line": 3088, + "line": 3423, "description": "assigns a canvas", "itemtype": "method", "name": "setCanvas", @@ -1630,7 +1690,31 @@ }, { "file": "../src/litegraph.js", - "line": 3341, + "line": 3498, + "description": "binds mouse, keyboard, touch and drag events to the canvas", + "itemtype": "method", + "name": "bindEvents", + "class": "LGraphCanvas" + }, + { + "file": "../src/litegraph.js", + "line": 3550, + "description": "unbinds mouse events from the canvas", + "itemtype": "method", + "name": "unbindEvents", + "class": "LGraphCanvas" + }, + { + "file": "../src/litegraph.js", + "line": 3598, + "description": "this function allows to render the canvas using WebGL instead of Canvas2D\nthis is useful if you plant to render 3D objects inside your nodes, it uses litegl.js for webgl and canvas2DtoWebGL to emulate the Canvas2D calls in webGL", + "itemtype": "method", + "name": "enableWebGL", + "class": "LGraphCanvas" + }, + { + "file": "../src/litegraph.js", + "line": 3640, "description": "Used to attach the canvas in a popup", "itemtype": "method", "name": "getCanvasWindow", @@ -1642,7 +1726,7 @@ }, { "file": "../src/litegraph.js", - "line": 3355, + "line": 3654, "description": "starts rendering the content of the canvas when needed", "itemtype": "method", "name": "startRendering", @@ -1650,11 +1734,275 @@ }, { "file": "../src/litegraph.js", - "line": 3379, + "line": 3678, "description": "stops rendering the content of the canvas (to save resources)", "itemtype": "method", "name": "stopRendering", "class": "LGraphCanvas" + }, + { + "file": "../src/litegraph.js", + "line": 3921, + "description": "Called when a mouse move event has to be processed", + "itemtype": "method", + "name": "processMouseMove", + "class": "LGraphCanvas" + }, + { + "file": "../src/litegraph.js", + "line": 4112, + "description": "Called when a mouse up event has to be processed", + "itemtype": "method", + "name": "processMouseUp", + "class": "LGraphCanvas" + }, + { + "file": "../src/litegraph.js", + "line": 4263, + "description": "Called when a mouse wheel event has to be processed", + "itemtype": "method", + "name": "processMouseWheel", + "class": "LGraphCanvas" + }, + { + "file": "../src/litegraph.js", + "line": 4296, + "description": "retuns true if a position (in graph space) is on top of a node little corner box", + "itemtype": "method", + "name": "isOverNodeBox", + "class": "LGraphCanvas" + }, + { + "file": "../src/litegraph.js", + "line": 4308, + "description": "retuns true if a position (in graph space) is on top of a node input slot", + "itemtype": "method", + "name": "isOverNodeInput", + "class": "LGraphCanvas" + }, + { + "file": "../src/litegraph.js", + "line": 4332, + "description": "process a key event", + "itemtype": "method", + "name": "processKey", + "class": "LGraphCanvas" + }, + { + "file": "../src/litegraph.js", + "line": 4485, + "description": "process a item drop event on top the canvas", + "itemtype": "method", + "name": "processDrop", + "class": "LGraphCanvas" + }, + { + "file": "../src/litegraph.js", + "line": 4603, + "description": "selects a given node (or adds it to the current selection)", + "itemtype": "method", + "name": "selectNode", + "class": "LGraphCanvas" + }, + { + "file": "../src/litegraph.js", + "line": 4615, + "description": "selects several nodes (or adds them to the current selection)", + "itemtype": "method", + "name": "selectNodes", + "class": "LGraphCanvas" + }, + { + "file": "../src/litegraph.js", + "line": 4653, + "description": "removes a node from the current selection", + "itemtype": "method", + "name": "deselectNode", + "class": "LGraphCanvas" + }, + { + "file": "../src/litegraph.js", + "line": 4679, + "description": "removes all nodes from the current selection", + "itemtype": "method", + "name": "deselectAllNodes", + "class": "LGraphCanvas" + }, + { + "file": "../src/litegraph.js", + "line": 4702, + "description": "deletes all nodes in the current selection from the graph", + "itemtype": "method", + "name": "deleteSelectedNodes", + "class": "LGraphCanvas" + }, + { + "file": "../src/litegraph.js", + "line": 4719, + "description": "centers the camera on a given node", + "itemtype": "method", + "name": "centerOnNode", + "class": "LGraphCanvas" + }, + { + "file": "../src/litegraph.js", + "line": 4730, + "description": "adds some useful properties to a mouse event, like the position in graph coordinates", + "itemtype": "method", + "name": "adjustMouseEvent", + "class": "LGraphCanvas" + }, + { + "file": "../src/litegraph.js", + "line": 4758, + "description": "changes the zoom level of the graph (default is 1), you can pass also a place used to pivot the zoom", + "itemtype": "method", + "name": "setZoom", + "class": "LGraphCanvas" + }, + { + "file": "../src/litegraph.js", + "line": 4786, + "description": "converts a coordinate in canvas2D space to graphcanvas space (NAME IS CONFUSION, SHOULD BE THE OTHER WAY AROUND)", + "itemtype": "method", + "name": "convertOffsetToCanvas", + "class": "LGraphCanvas" + }, + { + "file": "../src/litegraph.js", + "line": 4798, + "description": "converts a coordinate in graphcanvas space to canvas2D space (NAME IS CONFUSION, SHOULD BE THE OTHER WAY AROUND)", + "itemtype": "method", + "name": "convertCanvasToOffset", + "class": "LGraphCanvas" + }, + { + "file": "../src/litegraph.js", + "line": 4816, + "description": "brings a node to front (above all other nodes)", + "itemtype": "method", + "name": "bringToFront", + "class": "LGraphCanvas" + }, + { + "file": "../src/litegraph.js", + "line": 4829, + "description": "sends a node to the back (below all other nodes)", + "itemtype": "method", + "name": "sendToBack", + "class": "LGraphCanvas" + }, + { + "file": "../src/litegraph.js", + "line": 4849, + "description": "checks which nodes are visible (inside the camera area)", + "itemtype": "method", + "name": "computeVisibleNodes", + "class": "LGraphCanvas" + }, + { + "file": "../src/litegraph.js", + "line": 4874, + "description": "renders the whole canvas content, by rendering in two separated canvas, one containing the background grid and the connections, and one containing the nodes)", + "itemtype": "method", + "name": "draw", + "class": "LGraphCanvas" + }, + { + "file": "../src/litegraph.js", + "line": 4905, + "description": "draws the front canvas (the one containing all the nodes)", + "itemtype": "method", + "name": "drawFrontCanvas", + "class": "LGraphCanvas" + }, + { + "file": "../src/litegraph.js", + "line": 5038, + "description": "draws some useful stats in the corner of the canvas", + "itemtype": "method", + "name": "renderInfo", + "class": "LGraphCanvas" + }, + { + "file": "../src/litegraph.js", + "line": 5064, + "description": "draws the back canvas (the one containing the background and the connections)", + "itemtype": "method", + "name": "drawBackCanvas", + "class": "LGraphCanvas" + }, + { + "file": "../src/litegraph.js", + "line": 5196, + "description": "draws the given node inside the canvas", + "itemtype": "method", + "name": "drawNode", + "class": "LGraphCanvas" + }, + { + "file": "../src/litegraph.js", + "line": 5467, + "description": "draws the shape of the given node in the canvas", + "itemtype": "method", + "name": "drawNodeShape", + "class": "LGraphCanvas" + }, + { + "file": "../src/litegraph.js", + "line": 5639, + "description": "draws every connection visible in the canvas\nOPTIMIZE THIS: precatch connections position instead of recomputing them every time", + "itemtype": "method", + "name": "drawConnections", + "class": "LGraphCanvas" + }, + { + "file": "../src/litegraph.js", + "line": 5694, + "description": "draws a link between two points", + "itemtype": "method", + "name": "renderLink", + "class": "LGraphCanvas" + }, + { + "file": "../src/litegraph.js", + "line": 5815, + "description": "draws the widgets stored inside a node", + "itemtype": "method", + "name": "drawNodeWidgets", + "class": "LGraphCanvas" + }, + { + "file": "../src/litegraph.js", + "line": 5911, + "description": "process an event on widgets", + "itemtype": "method", + "name": "processNodeWidgets", + "class": "LGraphCanvas" + }, + { + "file": "../src/litegraph.js", + "line": 5990, + "description": "draws every group area in the background", + "itemtype": "method", + "name": "drawGroups", + "class": "LGraphCanvas" + }, + { + "file": "../src/litegraph.js", + "line": 6035, + "description": "resizes the canvas to a given size, if no size is passed, then it tries to fill the parentNode", + "itemtype": "method", + "name": "resize", + "class": "LGraphCanvas" + }, + { + "file": "../src/litegraph.js", + "line": 6058, + "description": "switches to live mode (node shapes are not rendered, only the content)\nthis feature was designed when graphs where meant to create user interfaces", + "itemtype": "method", + "name": "switchLiveMode", + "class": "LGraphCanvas" } ], "warnings": [] diff --git a/doc/files/.._src_litegraph.js.html b/doc/files/.._src_litegraph.js.html index 079c1d3ef..e1f237abe 100644 --- a/doc/files/.._src_litegraph.js.html +++ b/doc/files/.._src_litegraph.js.html @@ -84,6597 +84,7620 @@
      -(function(global){
      -// *************************************************************
      -//   LiteGraph CLASS                                     *******
      -// *************************************************************
      -
      -/* FYI: links are stored in graph.links with this structure per object
      -{
      -	id: number
      -	type: string,
      -	origin_id: number,
      -	origin_slot: number,
      -	target_id: number,
      -	target_slot: number,
      -	data: *
      -};
      -*/
      -
      -/**
      -* The Global Scope. It contains all the registered node classes.
      -*
      -* @class LiteGraph
      -* @constructor
      -*/
      -
      -var LiteGraph = global.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: 1000, //avoid infinite loops
      -	DEFAULT_POSITION: [100,100],//default node position
      -	node_images_path: "",
      -
      -	VALID_SHAPES: ["box","round"], //,"circle"
      -
      -	BOX_SHAPE: 1,
      -	ROUND_SHAPE: 2,
      -	CIRCLE_SHAPE: 3,
      -
      -	//enums
      -	INPUT: 1,
      -	OUTPUT: 2,
      -
      -	EVENT: -1, //for outputs
      -	ACTION: -1, //for inputs
      -
      -	ALWAYS: 0,
      -	ON_EVENT: 1,
      -	NEVER: 2,
      -	ON_TRIGGER: 3,
      -
      -	proxy: null, //used to redirect calls
      -
      -	debug: false,
      -	throw_errors: true,
      -	allow_scripts: true,
      -	registered_node_types: {}, //nodetypes by string
      -	node_types_by_file_extension: {}, //used for droping files in the canvas
      -	Nodes: {}, //node types by classname
      -
      -	/**
      -	* Register a node class so it can be listed when the user wants to create a new one
      -	* @method registerNodeType
      -	* @param {String} type name of the node and path
      -	* @param {Class} base_class class containing the structure of a node
      -	*/
      -
      -	registerNodeType: function(type, base_class)
      -	{
      -		if(!base_class.prototype)
      -			throw("Cannot register a simple object, it must be a class with a prototype");
      -		base_class.type = type;
      -
      -		if(LiteGraph.debug)
      -			console.log("Node registered: " + type);
      -
      -		var categories = type.split("/");
      -		var classname = base_class.name;
      -
      -		var pos = type.lastIndexOf("/");
      -		base_class.category = type.substr(0,pos);
      -
      -		if(!base_class.title)
      -			base_class.title = classname;
      -		//info.name = name.substr(pos+1,name.length - pos);
      -
      -		//extend class
      -		if(base_class.prototype) //is a class
      -			for(var i in LGraphNode.prototype)
      -				if(!base_class.prototype[i])
      -					base_class.prototype[i] = LGraphNode.prototype[i];
      -
      -		Object.defineProperty( base_class.prototype, "shape",{
      -			set: function(v) {
      -				switch(v)
      -				{
      -					case "box": this._shape = LiteGraph.BOX_SHAPE; break;
      -					case "round": this._shape = LiteGraph.ROUND_SHAPE; break;
      -					case "circle": this._shape = LiteGraph.CIRCLE_SHAPE; break;
      -					default:
      -						this._shape = v;
      -				}
      -			},
      -			get: function(v)
      -			{
      -				return this._shape;
      -			},
      -			enumerable: true
      -		});
      -
      -		this.registered_node_types[ type ] = base_class;
      -		if(base_class.constructor.name)
      -			this.Nodes[ classname ] = base_class;
      -
      -		//warnings
      -		if(base_class.prototype.onPropertyChange)
      -			console.warn("LiteGraph node class " + type + " has onPropertyChange method, it must be called onPropertyChanged with d at the end");
      -
      -		if( base_class.supported_extensions )
      -		{
      -			for(var i in base_class.supported_extensions )
      -				this.node_types_by_file_extension[ base_class.supported_extensions[i].toLowerCase() ] = base_class;
      -		}
      -	},
      -
      -	/**
      -	* Create a new node type by passing a function, it wraps it with a propper class and generates inputs according to the parameters of the function.
      -	* Useful to wrap simple methods that do not require properties, and that only process some input to generate an output.
      -	* @method wrapFunctionAsNode
      -	* @param {String} name node name with namespace (p.e.: 'math/sum')
      -	* @param {Function} func
      -	* @param {Array} param_types [optional] an array containing the type of every parameter, otherwise parameters will accept any type
      -	* @param {String} return_type [optional] string with the return type, otherwise it will be generic
      -	*/
      -	wrapFunctionAsNode: function( name, func, param_types, return_type )
      -	{
      -		var params = Array(func.length);
      -		var code = "";
      -		var names = LiteGraph.getParameterNames( func );
      -		for(var i = 0; i < names.length; ++i)
      -			code += "this.addInput('"+names[i]+"',"+(param_types && param_types[i] ? "'" + param_types[i] + "'" : "0") + ");\n";
      -		code += "this.addOutput('out',"+( return_type ? "'" + return_type + "'" : 0 )+");\n";
      -		var classobj = Function(code);
      -		classobj.title = name.split("/").pop();
      -		classobj.desc = "Generated from " + func.name;
      -		classobj.prototype.onExecute = function onExecute()
      -		{
      -			for(var i = 0; i < params.length; ++i)
      -				params[i] = this.getInputData(i);
      -			var r = func.apply( this, params );
      -			this.setOutputData(0,r);
      -		}
      -		this.registerNodeType( name, classobj );
      -	},
      -
      -	/**
      -	* Adds this method to all nodetypes, existing and to be created
      -	* (You can add it to LGraphNode.prototype but then existing node types wont have it)
      -	* @method addNodeMethod
      -	* @param {Function} func
      -	*/
      -	addNodeMethod: function( name, func )
      -	{
      -		LGraphNode.prototype[name] = func;
      -		for(var i in this.registered_node_types)
      -		{
      -			var type = this.registered_node_types[i];
      -			if(type.prototype[name])
      -				type.prototype["_" + name] = type.prototype[name]; //keep old in case of replacing
      -			type.prototype[name] = func;
      -		}
      -	},
      -
      -	/**
      -	* Create a node of a given type with a name. The node is not attached to any graph yet.
      -	* @method createNode
      -	* @param {String} type full name of the node class. p.e. "math/sin"
      -	* @param {String} name a name to distinguish from other nodes
      -	* @param {Object} options to set options
      -	*/
      -
      -	createNode: function( type, title, options )
      -	{
      -		var base_class = this.registered_node_types[type];
      -		if (!base_class)
      -		{
      -			if(LiteGraph.debug)
      -				console.log("GraphNode type \"" + type + "\" not registered.");
      -			return null;
      -		}
      -
      -		var prototype = base_class.prototype || base_class;
      -
      -		title = title || base_class.title || type;
      -
      -		var node = new base_class( title );
      -		node.type = type;
      -
      -		if(!node.title && title) node.title = title;
      -		if(!node.properties) node.properties = {};
      -		if(!node.properties_info) node.properties_info = [];
      -		if(!node.flags) node.flags = {};
      -		if(!node.size) node.size = node.computeSize();
      -		if(!node.pos) node.pos = LiteGraph.DEFAULT_POSITION.concat();
      -		if(!node.mode) node.mode = LiteGraph.ALWAYS;
      -
      -		//extra options
      -		if(options)
      -		{
      -			for(var i in options)
      -				node[i] = options[i];
      -		}
      -
      -		return node;
      -	},
      -
      -	/**
      -	* Returns a registered node type with a given name
      -	* @method getNodeType
      -	* @param {String} type full name of the node class. p.e. "math/sin"
      -	* @return {Class} the node class
      -	*/
      -
      -	getNodeType: function(type)
      -	{
      -		return this.registered_node_types[type];
      -	},
      -
      -
      -	/**
      -	* Returns a list of node types matching one category
      -	* @method getNodeType
      -	* @param {String} category category name
      -	* @return {Array} array with all the node classes
      -	*/
      -
      -	getNodeTypesInCategory: function(category)
      -	{
      -		var r = [];
      -		for(var i in this.registered_node_types)
      -			if(category == "")
      -			{
      -				if (this.registered_node_types[i].category == null)
      -					r.push(this.registered_node_types[i]);
      -			}
      -			else if (this.registered_node_types[i].category == category)
      -				r.push(this.registered_node_types[i]);
      -
      -		return r;
      -	},
      -
      -	/**
      -	* Returns a list with all the node type categories
      -	* @method getNodeTypesCategories
      -	* @return {Array} array with all the names of the categories
      -	*/
      -
      -	getNodeTypesCategories: function()
      -	{
      -		var categories = {"":1};
      -		for(var i in this.registered_node_types)
      -			if(this.registered_node_types[i].category && !this.registered_node_types[i].skip_list)
      -				categories[ this.registered_node_types[i].category ] = 1;
      -		var result = [];
      -		for(var i in categories)
      -			result.push(i);
      -		return result;
      -	},
      -
      -	//debug purposes: reloads all the js scripts that matches a wilcard
      -	reloadNodes: function (folder_wildcard)
      -	{
      -		var tmp = document.getElementsByTagName("script");
      -		//weird, this array changes by its own, so we use a copy
      -		var script_files = [];
      -		for(var i in tmp)
      -			script_files.push(tmp[i]);
      -
      -
      -		var docHeadObj = document.getElementsByTagName("head")[0];
      -		folder_wildcard = document.location.href + folder_wildcard;
      -
      -		for(var i in script_files)
      -		{
      -			var src = script_files[i].src;
      -			if( !src || src.substr(0,folder_wildcard.length ) != folder_wildcard)
      -				continue;
      -
      -			try
      -			{
      -				if(LiteGraph.debug)
      -					console.log("Reloading: " + src);
      -				var dynamicScript = document.createElement("script");
      -				dynamicScript.type = "text/javascript";
      -				dynamicScript.src = src;
      -				docHeadObj.appendChild(dynamicScript);
      -				docHeadObj.removeChild(script_files[i]);
      -			}
      -			catch (err)
      -			{
      -				if(LiteGraph.throw_errors)
      -					throw err;
      -				if(LiteGraph.debug)
      -					console.log("Error while reloading " + src);
      -			}
      -		}
      -
      -		if(LiteGraph.debug)
      -			console.log("Nodes reloaded");
      -	},
      -
      -	//separated just to improve if it doesnt work
      -	cloneObject: function(obj, target)
      -	{
      -		if(obj == null) return null;
      -		var r = JSON.parse( JSON.stringify( obj ) );
      -		if(!target) return r;
      -
      -		for(var i in r)
      -			target[i] = r[i];
      -		return target;
      -	},
      -
      -	isValidConnection: function( type_a, type_b )
      -	{
      -		if( !type_a ||  //generic output
      -			!type_b || //generic input
      -			type_a == type_b || //same type (is valid for triggers)
      -			type_a == LiteGraph.EVENT && type_b == LiteGraph.ACTION )
      -				return true;
      -
      -		type_a = type_a.toLowerCase();
      -		type_b = type_b.toLowerCase();
      -		if( type_a.indexOf(",") == -1 && type_b.indexOf(",") == -1 )
      -			return type_a == type_b;
      -
      -		var supported_types_a = type_a.split(",");
      -		var supported_types_b = type_b.split(",");
      -		for(var i = 0; i < supported_types_a.length; ++i)
      -			for(var j = 0; j < supported_types_b.length; ++j)
      -				if( supported_types_a[i] == supported_types_b[j] )
      -					return true;
      -		return false;
      -	}
      -};
      -
      -//timer that works everywhere
      -if(typeof(performance) != "undefined")
      -	LiteGraph.getTime = performance.now.bind(performance);
      -else if(typeof(Date) != "undefined" && Date.now)
      -	LiteGraph.getTime = Date.now.bind(Date);
      -else if(typeof(process) != "undefined")
      -	LiteGraph.getTime = function(){
      -		var t = process.hrtime();
      -		return t[0]*0.001 + t[1]*(1e-6);
      -	}
      -else
      -  LiteGraph.getTime = function getTime() { return (new Date).getTime(); }
      -
      -
      -
      -
      -
      -
      -//*********************************************************************************
      -// LGraph CLASS
      -//*********************************************************************************
      -
      -/**
      -* 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.
      -*
      -* @class LGraph
      -* @constructor
      -*/
      -
      -function LGraph()
      -{
      -	if (LiteGraph.debug)
      -		console.log("Graph created");
      -	this.list_of_graphcanvas = null;
      -	this.clear();
      -}
      -
      -global.LGraph = LiteGraph.LGraph = LGraph;
      -
      -//default supported types
      -LGraph.supported_types = ["number","string","boolean"];
      -
      -//used to know which types of connections support this graph (some graphs do not allow certain types)
      -LGraph.prototype.getSupportedTypes = function() { return this.supported_types || LGraph.supported_types; }
      -
      -LGraph.STATUS_STOPPED = 1;
      -LGraph.STATUS_RUNNING = 2;
      -
      -/**
      -* Removes all nodes from this graph
      -* @method clear
      -*/
      -
      -LGraph.prototype.clear = function()
      -{
      -	this.stop();
      -	this.status = LGraph.STATUS_STOPPED;
      -	this.last_node_id = 0;
      -
      -	//nodes
      -	this._nodes = [];
      -	this._nodes_by_id = {};
      -	this._nodes_in_order = null; //nodes that are executable sorted in execution order
      -	this._nodes_executable = null; //nodes that contain onExecute
      -
      -	//links
      -	this.last_link_id = 0;
      -	this.links = {}; //container with all the links
      -
      -	//iterations
      -	this.iteration = 0;
      -
      -	this.config = {
      -	};
      -
      -	//timing
      -	this.globaltime = 0;
      -	this.runningtime = 0;
      -	this.fixedtime =  0;
      -	this.fixedtime_lapse = 0.01;
      -	this.elapsed_time = 0.01;
      -	this.starttime = 0;
      -
      -	this.catch_errors = true;
      -
      -	//subgraph_data
      -	this.global_inputs = {};
      -	this.global_outputs = {};
      -
      -	//this.graph = {};
      -	this.debug = true;
      -
      -	this.change();
      -
      -	this.sendActionToCanvas("clear");
      -}
      -
      -/**
      -* Attach Canvas to this graph
      -* @method attachCanvas
      -* @param {GraphCanvas} graph_canvas
      -*/
      -
      -LGraph.prototype.attachCanvas = function(graphcanvas)
      -{
      -	if(graphcanvas.constructor != LGraphCanvas)
      -		throw("attachCanvas expects a LGraphCanvas instance");
      -	if(graphcanvas.graph && graphcanvas.graph != this)
      -		graphcanvas.graph.detachCanvas( graphcanvas );
      -
      -	graphcanvas.graph = this;
      -	if(!this.list_of_graphcanvas)
      -		this.list_of_graphcanvas = [];
      -	this.list_of_graphcanvas.push(graphcanvas);
      -}
      -
      -/**
      -* Detach Canvas from this graph
      -* @method detachCanvas
      -* @param {GraphCanvas} graph_canvas
      -*/
      -
      -LGraph.prototype.detachCanvas = function(graphcanvas)
      -{
      -	if(!this.list_of_graphcanvas)
      -		return;
      -
      -	var pos = this.list_of_graphcanvas.indexOf( graphcanvas );
      -	if(pos == -1)
      -		return;
      -	graphcanvas.graph = null;
      -	this.list_of_graphcanvas.splice(pos,1);
      -}
      -
      -/**
      -* Starts running this graph every interval milliseconds.
      -* @method start
      -* @param {number} interval amount of milliseconds between executions, default is 1
      -*/
      -
      -LGraph.prototype.start = function(interval)
      -{
      -	if( this.status == LGraph.STATUS_RUNNING )
      -		return;
      -	this.status = LGraph.STATUS_RUNNING;
      -
      -	if(this.onPlayEvent)
      -		this.onPlayEvent();
      -
      -	this.sendEventToAllNodes("onStart");
      -
      -	//launch
      -	this.starttime = LiteGraph.getTime();
      -	interval = interval || 1;
      -	var that = this;
      -
      -	this.execution_timer_id = setInterval( function() {
      -		//execute
      -		that.runStep(1, !this.catch_errors );
      -	},interval);
      -}
      -
      -/**
      -* Stops the execution loop of the graph
      -* @method stop execution
      -*/
      -
      -LGraph.prototype.stop = function()
      -{
      -	if( this.status == LGraph.STATUS_STOPPED )
      -		return;
      -
      -	this.status = LGraph.STATUS_STOPPED;
      -
      -	if(this.onStopEvent)
      -		this.onStopEvent();
      -
      -	if(this.execution_timer_id != null)
      -		clearInterval(this.execution_timer_id);
      -	this.execution_timer_id = null;
      -
      -	this.sendEventToAllNodes("onStop");
      -}
      -
      -/**
      -* Run N steps (cycles) of the graph
      -* @method runStep
      -* @param {number} num number of steps to run, default is 1
      -*/
      -
      -LGraph.prototype.runStep = function( num, do_not_catch_errors )
      -{
      -	num = num || 1;
      -
      -	var start = LiteGraph.getTime();
      -	this.globaltime = 0.001 * (start - this.starttime);
      -
      -	var nodes = this._nodes_executable ? this._nodes_executable : this._nodes;
      -	if(!nodes)
      -		return;
      -
      -	if( do_not_catch_errors )
      -	{
      -		//iterations
      -		for(var i = 0; i < num; i++)
      -		{
      -			for( var j = 0, l = nodes.length; j < l; ++j )
      -			{
      -				var node = nodes[j];
      -				if( node.mode == LiteGraph.ALWAYS && node.onExecute )
      -					node.onExecute();
      -			}
      -
      -			this.fixedtime += this.fixedtime_lapse;
      -			if( this.onExecuteStep )
      -				this.onExecuteStep();
      -		}
      -
      -		if( this.onAfterExecute )
      -			this.onAfterExecute();
      -	}
      -	else
      -	{
      -		try
      -		{
      -			//iterations
      -			for(var i = 0; i < num; i++)
      -			{
      -				for( var j = 0, l = nodes.length; j < l; ++j )
      -				{
      -					var node = nodes[j];
      -					if( node.mode == LiteGraph.ALWAYS && node.onExecute )
      -						node.onExecute();
      -				}
      -
      -				this.fixedtime += this.fixedtime_lapse;
      -				if( this.onExecuteStep )
      -					this.onExecuteStep();
      -			}
      -
      -			if( this.onAfterExecute )
      -				this.onAfterExecute();
      -			this.errors_in_execution = false;
      -		}
      -		catch (err)
      -		{
      -			this.errors_in_execution = true;
      -			if(LiteGraph.throw_errors)
      -				throw err;
      -			if(LiteGraph.debug)
      -				console.log("Error during execution: " + err);
      -			this.stop();
      -		}
      -	}
      -
      -	var elapsed = LiteGraph.getTime() - start;
      -	if (elapsed == 0)
      -		elapsed = 1;
      -	this.elapsed_time = 0.001 * elapsed;
      -	this.globaltime += 0.001 * elapsed;
      -	this.iteration += 1;
      -}
      -
      -/**
      -* Updates the graph execution order according to relevance of the nodes (nodes with only outputs have more relevance than
      -* nodes with only inputs.
      -* @method updateExecutionOrder
      -*/
      -LGraph.prototype.updateExecutionOrder = function()
      -{
      -	this._nodes_in_order = this.computeExecutionOrder( false );
      -	this._nodes_executable = [];
      -	for(var i = 0; i < this._nodes_in_order.length; ++i)
      -		if( this._nodes_in_order[i].onExecute )
      -			this._nodes_executable.push( this._nodes_in_order[i] );
      -}
      -
      -//This is more internal, it computes the order and returns it
      -LGraph.prototype.computeExecutionOrder = function( only_onExecute, set_level )
      -{
      -	var L = [];
      -	var S = [];
      -	var M = {};
      -	var visited_links = {}; //to avoid repeating links
      -	var remaining_links = {}; //to a
      -
      -	//search for the nodes without inputs (starting nodes)
      -	for (var i = 0, l = this._nodes.length; i < l; ++i)
      -	{
      -		var node = this._nodes[i];
      -		if( only_onExecute && !node.onExecute )
      -			continue;
      -
      -		M[node.id] = node; //add to pending nodes
      -
      -		var num = 0; //num of input connections
      -		if(node.inputs)
      -			for(var j = 0, l2 = node.inputs.length; j < l2; j++)
      -				if(node.inputs[j] && node.inputs[j].link != null)
      -					num += 1;
      -
      -		if(num == 0) //is a starting node
      -		{
      -			S.push(node);
      -			if(set_level)
      -				node._level = 1;
      -		}
      -		else //num of input links
      -		{
      -			if(set_level)
      -				node._level = 0;
      -			remaining_links[node.id] = num;
      -		}
      -	}
      -
      -	while(true)
      -	{
      -		if(S.length == 0)
      -			break;
      -
      -		//get an starting node
      -		var node = S.shift();
      -		L.push(node); //add to ordered list
      -		delete M[node.id]; //remove from the pending nodes
      -
      -		if(!node.outputs)
      -			continue;
      -
      -		//for every output
      -		for(var i = 0; i < node.outputs.length; i++)
      -		{
      -			var output = node.outputs[i];
      -			//not connected
      -			if(output == null || output.links == null || output.links.length == 0)
      -				continue;
      -
      -			//for every connection
      -			for(var j = 0; j < output.links.length; j++)
      -			{
      -				var link_id = output.links[j];
      -				var link = this.links[link_id];
      -				if(!link)
      -					continue;
      -
      -				//already visited link (ignore it)
      -				if(visited_links[ link.id ])
      -					continue;
      -
      -				var target_node = this.getNodeById( link.target_id );
      -				if(target_node == null)
      -				{
      -					visited_links[ link.id ] = true;
      -					continue;
      -				}
      -
      -				if(set_level && (!target_node._level || target_node._level <= node._level))
      -					target_node._level = node._level + 1;
      -
      -				visited_links[link.id] = true; //mark as visited
      -				remaining_links[target_node.id] -= 1; //reduce the number of links remaining
      -				if (remaining_links[ target_node.id ] == 0)
      -					S.push(target_node); //if no more links, then add to starters array
      -			}
      -		}
      -	}
      -
      -	//the remaining ones (loops)
      -	for(var i in M)
      -		L.push( M[i] );
      -
      -	if( L.length != this._nodes.length && LiteGraph.debug )
      -		console.warn("something went wrong, nodes missing");
      -
      -	//save order number in the node
      -	for(var i = 0; i < L.length; ++i)
      -		L[i].order = i;
      -
      -	return L;
      -}
      -
      -/**
      -* Positions every node in a more readable manner
      -* @method arrange
      -*/
      -LGraph.prototype.arrange = function( margin )
      -{
      -	margin = margin || 40;
      -
      -	var nodes = this.computeExecutionOrder( false, true );
      -	var columns = [];
      -	for(var i = 0; i < nodes.length; ++i)
      -	{
      -		var node = nodes[i];
      -		var col = node._level || 1;
      -		if(!columns[col])
      -			columns[col] = [];
      -		columns[col].push( node );
      -	}
      -
      -	var x = margin;
      -
      -	for(var i = 0; i < columns.length; ++i)
      -	{
      -		var column = columns[i];
      -		if(!column)
      -			continue;
      -		var max_size = 100;
      -		var y = margin;
      -		for(var j = 0; j < column.length; ++j)
      -		{
      -			var node = column[j];
      -			node.pos[0] = x;
      -			node.pos[1] = y;
      -			if(node.size[0] > max_size)
      -				max_size = node.size[0];
      -			y += node.size[1] + margin;
      -		}
      -		x += max_size + margin;
      -	}
      -
      -	this.setDirtyCanvas(true,true);
      -}
      -
      -
      -/**
      -* Returns the amount of time the graph has been running in milliseconds
      -* @method getTime
      -* @return {number} number of milliseconds the graph has been running
      -*/
      -LGraph.prototype.getTime = function()
      -{
      -	return this.globaltime;
      -}
      -
      -/**
      -* Returns the amount of time accumulated using the fixedtime_lapse var. This is used in context where the time increments should be constant
      -* @method getFixedTime
      -* @return {number} number of milliseconds the graph has been running
      -*/
      -
      -LGraph.prototype.getFixedTime = function()
      -{
      -	return this.fixedtime;
      -}
      -
      -/**
      -* Returns the amount of time it took to compute the latest iteration. Take into account that this number could be not correct
      -* if the nodes are using graphical actions
      -* @method getElapsedTime
      -* @return {number} number of milliseconds it took the last cycle
      -*/
      -
      -LGraph.prototype.getElapsedTime = function()
      -{
      -	return this.elapsed_time;
      -}
      -
      -/**
      -* Sends an event to all the nodes, useful to trigger stuff
      -* @method sendEventToAllNodes
      -* @param {String} eventname the name of the event (function to be called)
      -* @param {Array} params parameters in array format
      -*/
      -
      -LGraph.prototype.sendEventToAllNodes = function( eventname, params, mode )
      -{
      -	mode = mode || LiteGraph.ALWAYS;
      -
      -	var nodes = this._nodes_in_order ? this._nodes_in_order : this._nodes;
      -	if(!nodes)
      -		return;
      -
      -	for( var j = 0, l = nodes.length; j < l; ++j )
      -	{
      -		var node = nodes[j];
      -		if(node[eventname] && node.mode == mode )
      -		{
      -			if(params === undefined)
      -				node[eventname]();
      -			else if(params && params.constructor === Array)
      -				node[eventname].apply( node, params );
      -			else
      -				node[eventname](params);
      -		}
      -	}
      -}
      -
      -LGraph.prototype.sendActionToCanvas = function(action, params)
      -{
      -	if(!this.list_of_graphcanvas)
      -		return;
      -
      -	for(var i = 0; i < this.list_of_graphcanvas.length; ++i)
      -	{
      -		var c = this.list_of_graphcanvas[i];
      -		if( c[action] )
      -			c[action].apply(c, params);
      -	}
      -}
      -
      -/**
      -* Adds a new node instasnce to this graph
      -* @method add
      -* @param {LGraphNode} node the instance of the node
      -*/
      -
      -LGraph.prototype.add = function(node, skip_compute_order)
      -{
      -	if(!node)
      -		return;
      -	if(node.id != -1 && this._nodes_by_id[node.id] != null)
      -	{
      -		console.warn("LiteGraph: there is already a node with this ID, changing it");
      -		node.id = ++this.last_node_id;
      -	}
      -
      -	if(this._nodes.length >= LiteGraph.MAX_NUMBER_OF_NODES)
      -		throw("LiteGraph: max number of nodes in a graph reached");
      -
      -	//give him an id
      -	if(node.id == null || node.id == -1)
      -		node.id = ++this.last_node_id;
      -	else if (this.last_node_id < node.id)
      -		this.last_node_id = node.id;
      -
      -	node.graph = this;
      -
      -	this._nodes.push(node);
      -	this._nodes_by_id[node.id] = node;
      -
      -	/*
      -	// rendering stuf...
      -	if(node.bgImageUrl)
      -		node.bgImage = node.loadImage(node.bgImageUrl);
      -	*/
      -
      -	if(node.onAdded)
      -		node.onAdded( this );
      -
      -	if(this.config.align_to_grid)
      -		node.alignToGrid();
      -
      -	if(!skip_compute_order)
      -		this.updateExecutionOrder();
      -
      -	if(this.onNodeAdded)
      -		this.onNodeAdded(node);
      -
      -
      -	this.setDirtyCanvas(true);
      -
      -	this.change();
      -
      -	return node; //to chain actions
      -}
      -
      -/**
      -* Removes a node from the graph
      -* @method remove
      -* @param {LGraphNode} node the instance of the node
      -*/
      -
      -LGraph.prototype.remove = function(node)
      -{
      -	if(this._nodes_by_id[node.id] == null)
      -		return; //not found
      -
      -	if(node.ignore_remove)
      -		return; //cannot be removed
      -
      -	//disconnect inputs
      -	if(node.inputs)
      -		for(var i = 0; i < node.inputs.length; i++)
      -		{
      -			var slot = node.inputs[i];
      -			if(slot.link != null)
      -				node.disconnectInput(i);
      -		}
      -
      -	//disconnect outputs
      -	if(node.outputs)
      -		for(var i = 0; i < node.outputs.length; i++)
      -		{
      -			var slot = node.outputs[i];
      -			if(slot.links != null && slot.links.length)
      -				node.disconnectOutput(i);
      -		}
      -
      -	//node.id = -1; //why?
      -
      -	//callback
      -	if(node.onRemoved)
      -		node.onRemoved();
      -
      -	node.graph = null;
      -
      -	//remove from canvas render
      -	if(this.list_of_graphcanvas)
      -	{
      -		for(var i = 0; i < this.list_of_graphcanvas.length; ++i)
      -		{
      -			var canvas = this.list_of_graphcanvas[i];
      -			if(canvas.selected_nodes[node.id])
      -				delete canvas.selected_nodes[node.id];
      -			if(canvas.node_dragged == node)
      -				canvas.node_dragged = null;
      -		}
      -	}
      -
      -	//remove from containers
      -	var pos = this._nodes.indexOf(node);
      -	if(pos != -1)
      -		this._nodes.splice(pos,1);
      -	delete this._nodes_by_id[node.id];
      -
      -	if(this.onNodeRemoved)
      -		this.onNodeRemoved(node);
      -
      -	this.setDirtyCanvas(true,true);
      -
      -	this.change();
      -
      -	this.updateExecutionOrder();
      -}
      -
      -/**
      -* Returns a node by its id.
      -* @method getNodeById
      -* @param {Number} id
      -*/
      -
      -LGraph.prototype.getNodeById = function( id )
      -{
      -	if( id == null )
      -		return null;
      -	return this._nodes_by_id[ id ];
      -}
      -
      -/**
      -* Returns a list of nodes that matches a class
      -* @method findNodesByClass
      -* @param {Class} classObject the class itself (not an string)
      -* @return {Array} a list with all the nodes of this type
      -*/
      -
      -LGraph.prototype.findNodesByClass = function(classObject)
      -{
      -	var r = [];
      -	for(var i = 0, l = this._nodes.length; i < l; ++i)
      -		if(this._nodes[i].constructor === classObject)
      -			r.push(this._nodes[i]);
      -	return r;
      -}
      -
      -/**
      -* Returns a list of nodes that matches a type
      -* @method findNodesByType
      -* @param {String} type the name of the node type
      -* @return {Array} a list with all the nodes of this type
      -*/
      -
      -LGraph.prototype.findNodesByType = function(type)
      -{
      -	var type = type.toLowerCase();
      -	var r = [];
      -	for(var i = 0, l = this._nodes.length; i < l; ++i)
      -		if(this._nodes[i].type.toLowerCase() == type )
      -			r.push(this._nodes[i]);
      -	return r;
      -}
      -
      -/**
      -* Returns a list of nodes that matches a name
      -* @method findNodesByTitle
      -* @param {String} name the name of the node to search
      -* @return {Array} a list with all the nodes with this name
      -*/
      -
      -LGraph.prototype.findNodesByTitle = function(title)
      -{
      -	var result = [];
      -	for(var i = 0, l = this._nodes.length; i < l; ++i)
      -		if(this._nodes[i].title == title)
      -			result.push(this._nodes[i]);
      -	return result;
      -}
      -
      -/**
      -* Returns the top-most node in this position of the canvas
      -* @method getNodeOnPos
      -* @param {number} x the x coordinate in canvas space
      -* @param {number} y the y coordinate in canvas space
      -* @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;
      -	for (var i = nodes_list.length - 1; i >= 0; i--)
      -	{
      -		var n = nodes_list[i];
      -		if(n.isPointInsideNode( x, y, 2 ))
      -			return n;
      -	}
      -	return null;
      -}
      -
      -// ********** GLOBALS *****************
      -
      -/**
      -* 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 };
      -
      -	if(this.onGlobalInputAdded)
      -		this.onGlobalInputAdded(name, type);
      -
      -	if(this.onGlobalsChange)
      -		this.onGlobalsChange();
      -}
      -
      -/**
      -* 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];
      -	if (!input)
      -		return;
      -	input.value = data;
      -}
      -
      -/**
      -* 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];
      -	if (!input)
      -		return null;
      -	return input.value;
      -}
      -
      -/**
      -* 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)
      -		return;
      -
      -	if(!this.global_inputs[old_name])
      -		return false;
      -
      -	if(this.global_inputs[name])
      -	{
      -		console.error("there is already one input with that name");
      -		return false;
      -	}
      -
      -	this.global_inputs[name] = this.global_inputs[old_name];
      -	delete this.global_inputs[old_name];
      -
      -	if(this.onGlobalInputRenamed)
      -		this.onGlobalInputRenamed(old_name, name);
      -
      -	if(this.onGlobalsChange)
      -		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])
      -		return false;
      -
      -	if(this.global_inputs[name].type.toLowerCase() == type.toLowerCase() )
      -		return;
      -
      -	this.global_inputs[name].type = type;
      -	if(this.onGlobalInputTypeChanged)
      -		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])
      -		return false;
      -
      -	delete this.global_inputs[name];
      -
      -	if(this.onGlobalInputRemoved)
      -		this.onGlobalInputRemoved(name);
      -
      -	if(this.onGlobalsChange)
      -		this.onGlobalsChange();
      -	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 };
      -
      -	if(this.onGlobalOutputAdded)
      -		this.onGlobalOutputAdded(name, type);
      -
      -	if(this.onGlobalsChange)
      -		this.onGlobalsChange();
      -}
      -
      -/**
      -* 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 ];
      -	if (!output)
      -		return;
      -	output.value = value;
      -}
      -
      -/**
      -* 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];
      -	if (!output)
      -		return null;
      -	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;
      -
      -
      -/**
      -* 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])
      -		return false;
      -
      -	if(this.global_outputs[name])
      -	{
      -		console.error("there is already one output with that name");
      -		return false;
      -	}
      -
      -	this.global_outputs[name] = this.global_outputs[old_name];
      -	delete this.global_outputs[old_name];
      -
      -	if(this.onGlobalOutputRenamed)
      -		this.onGlobalOutputRenamed(old_name, name);
      -
      -	if(this.onGlobalsChange)
      -		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])
      -		return false;
      -
      -	if(this.global_outputs[name].type.toLowerCase() == type.toLowerCase() )
      -		return;
      -
      -	this.global_outputs[name].type = type;
      -	if(this.onGlobalOutputTypeChanged)
      -		this.onGlobalOutputTypeChanged(name, type);
      -}
      -
      -/**
      -* Removes a global graph output
      -* @method removeGlobalOutput
      -* @param {String} name
      -*/
      -LGraph.prototype.removeGlobalOutput = function(name)
      -{
      -	if(!this.global_outputs[name])
      -		return false;
      -	delete this.global_outputs[name];
      -
      -	if(this.onGlobalOutputRemoved)
      -		this.onGlobalOutputRemoved(name);
      -
      -	if(this.onGlobalsChange)
      -		this.onGlobalsChange();
      -	return true;
      -}
      -
      -LGraph.prototype.triggerInput = function(name,value)
      -{
      -	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.findNodesByTitle(name);
      -	for(var i = 0; i < nodes.length; ++i)
      -		nodes[i].setTrigger(func);
      -}
      -
      -
      -LGraph.prototype.connectionChange = function( node )
      -{
      -	this.updateExecutionOrder();
      -	if( this.onConnectionChange )
      -		this.onConnectionChange( node );
      -	this.sendActionToCanvas("onConnectionChange");
      -}
      -
      -/**
      -* returns if the graph is in live mode
      -* @method isLive
      -*/
      -
      -LGraph.prototype.isLive = function()
      -{
      -	if(!this.list_of_graphcanvas)
      -		return false;
      -
      -	for(var i = 0; i < this.list_of_graphcanvas.length; ++i)
      -	{
      -		var c = this.list_of_graphcanvas[i];
      -		if(c.live_mode)
      -			return true;
      -	}
      -	return false;
      -}
      -
      -/* Called when something visually changed */
      -LGraph.prototype.change = function()
      -{
      -	if(LiteGraph.debug)
      -		console.log("Graph changed");
      -
      -	this.sendActionToCanvas("setDirty",[true,true]);
      -
      -	if(this.on_change)
      -		this.on_change(this);
      -}
      -
      -LGraph.prototype.setDirtyCanvas = function(fg,bg)
      -{
      -	this.sendActionToCanvas("setDirty",[fg,bg]);
      -}
      -
      -//save and recover app state ***************************************
      -/**
      -* Creates a Object containing all the info about this graph, it can be serialized
      -* @method serialize
      -* @return {Object} value of the node
      -*/
      -LGraph.prototype.serialize = function()
      -{
      -	var nodes_info = [];
      -	for(var i = 0, l = this._nodes.length; i < l; ++i)
      -		nodes_info.push( this._nodes[i].serialize() );
      -
      -	//pack link info into a non-verbose format
      -	var links = [];
      -	for(var i in this.links) //links is an OBJECT
      -	{
      -		var link = this.links[i];
      -		links.push([ link.id, link.origin_id, link.origin_slot, link.target_id, link.target_slot, link.type ]);
      -	}
      -
      -	var data = {
      -		iteration: this.iteration,
      -		frame: this.frame,
      -		last_node_id: this.last_node_id,
      -		last_link_id: this.last_link_id,
      -		links: links, //LiteGraph.cloneObject( this.links ),
      -		config: this.config,
      -		nodes: nodes_info
      -	};
      -
      -	return data;
      -}
      -
      -
      -/**
      -* Configure a graph from a JSON string
      -* @method configure
      -* @param {String} str configure a graph from a JSON string
      -*/
      -LGraph.prototype.configure = function(data, keep_old)
      -{
      -	if(!keep_old)
      -		this.clear();
      -
      -	var nodes = data.nodes;
      -
      -	//decode links info (they are very verbose)
      -	if(data.links && data.links.constructor === Array)
      -	{
      -		var links = {};
      -		for(var i = 0; i < data.links.length; ++i)
      -		{
      -			var link = data.links[i];
      -			links[ link[0] ] = { id: link[0], origin_id: link[1], origin_slot: link[2], target_id: link[3], target_slot: link[4], type: link[5] };
      -		}
      -		data.links = links;
      -	}
      -
      -	//copy all stored fields
      -	for (var i in data)
      -		this[i] = data[i];
      -
      -	var error = false;
      -
      -	//create nodes
      -	this._nodes = [];
      -	for(var i = 0, l = nodes.length; i < l; ++i)
      -	{
      -		var n_info = nodes[i]; //stored info
      -		var node = LiteGraph.createNode( n_info.type, n_info.title );
      -		if(!node)
      -		{
      -			if(LiteGraph.debug)
      -				console.log("Node not found: " + n_info.type);
      -			error = true;
      -			continue;
      -		}
      -
      -		node.id = n_info.id; //id it or it will create a new id
      -		this.add(node, true); //add before configure, otherwise configure cannot create links
      -	}
      -
      -	//configure nodes afterwards so they can reach each other
      -	for(var i = 0, l = nodes.length; i < l; ++i)
      -	{
      -		var n_info = nodes[i];
      -		var node = this.getNodeById( n_info.id );
      -		if(node)
      -			node.configure( n_info );
      -	}
      -
      -	this.updateExecutionOrder();
      -	this.setDirtyCanvas(true,true);
      -	return error;
      -}
      -
      -LGraph.prototype.load = function(url)
      -{
      -	var that = this;
      -	var req = new XMLHttpRequest();
      -	req.open('GET', url, true);
      -	req.send(null);
      -	req.onload = function (oEvent) {
      -		if(req.status !== 200)
      -		{
      -			console.error("Error loading graph:",req.status,req.response);
      -			return;
      -		}
      -		var data = JSON.parse( req.response );
      -		that.configure(data);
      -	}
      -	req.onerror = function(err)
      -	{
      -		console.error("Error loading graph:",err);
      -	}
      -}
      -
      -LGraph.prototype.onNodeTrace = function(node, msg, color)
      -{
      -	//TODO
      -}
      -
      -// *************************************************************
      -//   Node CLASS                                          *******
      -// *************************************************************
      -
      -/*
      -	title: string
      -	pos: [x,y]
      -	size: [x,y]
      -
      -	input|output: every connection
      -		+  { name:string, type:string, pos: [x,y]=Optional, direction: "input"|"output", links: Array });
      -
      -	flags:
      -		+ skip_title_render
      -		+ clip_area
      -		+ unsafe_execution: not allowed for safe execution
      -		+ skip_repeated_outputs: when adding new outputs, it wont show if there is one already connected
      -
      -	supported callbacks:
      -		+ onAdded: when added to graph
      -		+ onRemoved: when removed from graph
      -		+ onStart:	when the graph starts playing
      -		+ onStop:	when the graph stops playing
      -		+ onDrawForeground: render the inside widgets inside the node
      -		+ onDrawBackground: render the background area inside the node (only in edit mode)
      -		+ onMouseDown
      -		+ onMouseMove
      -		+ onMouseUp
      -		+ onMouseEnter
      -		+ onMouseLeave
      -		+ onExecute: execute the node
      -		+ onPropertyChanged: when a property is changed in the panel (return true to skip default behaviour)
      -		+ onGetInputs: returns an array of possible inputs
      -		+ onGetOutputs: returns an array of possible outputs
      -		+ onDblClick
      -		+ onSerialize
      -		+ onSelected
      -		+ onDeselected
      -		+ onDropItem : DOM item dropped over the node
      -		+ onDropFile : file dropped over the node
      -		+ onConnectInput : if returns false the incoming connection will be canceled
      -		+ onConnectionsChange : a connection changed (new one or removed) (LiteGraph.INPUT or LiteGraph.OUTPUT, slot, true if connected, link_info, input_info )
      -*/
      -
      -/**
      -* Base Class for all the node type classes
      -* @class LGraphNode
      -* @param {String} name a name for the node
      -*/
      -
      -function LGraphNode(title)
      -{
      -	this._ctor(title);
      -}
      -
      -global.LGraphNode = LiteGraph.LGraphNode = LGraphNode;
      -
      -LGraphNode.prototype._ctor = function( title )
      -{
      -	this.title = title || "Unnamed";
      -	this.size = [LiteGraph.NODE_WIDTH,60];
      -	this.graph = null;
      -
      -	this._pos = new Float32Array(10,10);
      -
      -	Object.defineProperty( this, "pos", {
      -		set: function(v)
      -		{
      -			if(!v || !v.length < 2)
      -				return;
      -			this._pos[0] = v[0];
      -			this._pos[1] = v[1];
      -		},
      -		get: function()
      -		{
      -			return this._pos;
      -		},
      -		enumerable: true
      -	});
      -
      -	this.id = -1; //not know till not added
      -	this.type = null;
      -
      -	//inputs available: array of inputs
      -	this.inputs = [];
      -	this.outputs = [];
      -	this.connections = [];
      -
      -	//local data
      -	this.properties = {}; //for the values
      -	this.properties_info = []; //for the info
      -
      -	this.data = null; //persistent local data
      -	this.flags = {
      -		//skip_title_render: true,
      -		//unsafe_execution: false,
      -	};
      -}
      -
      -/**
      -* configure a node from an object containing the serialized info
      -* @method configure
      -*/
      -LGraphNode.prototype.configure = function(info)
      -{
      -	for (var j in info)
      -	{
      -		if(j == "console")
      -			continue;
      -
      -		if(j == "properties")
      -		{
      -			//i dont want to clone properties, I want to reuse the old container
      -			for(var k in info.properties)
      -			{
      -				this.properties[k] = info.properties[k];
      -				if(this.onPropertyChanged)
      -					this.onPropertyChanged(k,info.properties[k]);
      -			}
      -			continue;
      -		}
      -
      -		if(info[j] == null)
      -			continue;
      -
      -		else if (typeof(info[j]) == 'object') //object
      -		{
      -			if(this[j] && this[j].configure)
      -				this[j].configure( info[j] );
      -			else
      -				this[j] = LiteGraph.cloneObject(info[j], this[j]);
      -		}
      -		else //value
      -			this[j] = info[j];
      -	}
      -
      -	if(!info.title)
      -		this.title = this.constructor.title;
      -
      -	if(this.onConnectionsChange)
      -	{
      -		if(this.inputs)
      -		for(var i = 0; i < this.inputs.length; ++i)
      -		{
      -			var input = this.inputs[i];
      -			var link_info = this.graph.links[ input.link ];
      -			this.onConnectionsChange( LiteGraph.INPUT, i, true, link_info, input ); //link_info has been created now, so its updated
      -		}
      -
      -		if(this.outputs)
      -		for(var i = 0; i < this.outputs.length; ++i)
      -		{
      -			var output = this.outputs[i];
      -			if(!output.links)
      -				continue;
      -			for(var j = 0; j < output.links.length; ++j)
      -			{
      -				var link_info = this.graph.links[ output.links[j] ];
      -				this.onConnectionsChange( LiteGraph.OUTPUT, i, true, link_info, output ); //link_info has been created now, so its updated
      -			}
      -		}
      -	}
      -
      -	//FOR LEGACY, PLEASE REMOVE ON NEXT VERSION
      -	for(var i in this.inputs)
      -	{
      -		var input = this.inputs[i];
      -		if(!input.link || !input.link.length )
      -			continue;
      -		var link = input.link;
      -		if(typeof(link) != "object")
      -			continue;
      -		input.link = link[0];
      -		this.graph.links[ link[0] ] = {
      -			id: link[0],
      -			origin_id: link[1],
      -			origin_slot: link[2],
      -			target_id: link[3],
      -			target_slot: link[4]
      -		};
      -	}
      -	for(var i in this.outputs)
      -	{
      -		var output = this.outputs[i];
      -		if(!output.links || output.links.length == 0)
      -			continue;
      -		for(var j in output.links)
      -		{
      -			var link = output.links[j];
      -			if(typeof(link) != "object")
      -				continue;
      -			output.links[j] = link[0];
      -		}
      -	}
      -
      -	if( this.onConfigure )
      -		this.onConfigure( info );
      -}
      -
      -/**
      -* serialize the content
      -* @method serialize
      -*/
      -
      -LGraphNode.prototype.serialize = function()
      -{
      -	//create serialization object
      -	var o = {
      -		id: this.id,
      -		type: this.type,
      -		pos: this.pos,
      -		size: this.size,
      -		data: this.data,
      -		flags: LiteGraph.cloneObject(this.flags),
      -		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);
      -
      -	if(!o.type)
      -		o.type = this.constructor.type;
      -
      -	if(this.color)
      -		o.color = this.color;
      -	if(this.bgcolor)
      -		o.bgcolor = this.bgcolor;
      -	if(this.boxcolor)
      -		o.boxcolor = this.boxcolor;
      -	if(this.shape)
      -		o.shape = this.shape;
      -
      -	if(this.onSerialize)
      -		this.onSerialize(o);
      -
      -	return o;
      -}
      -
      -
      -/* Creates a clone of this node */
      -LGraphNode.prototype.clone = function()
      -{
      -	var node = LiteGraph.createNode(this.type);
      -
      -	//we clone it because serialize returns shared containers
      -	var data = LiteGraph.cloneObject( this.serialize() );
      -
      -	//remove links
      -	if(data.inputs)
      -		for(var i = 0; i < data.inputs.length; ++i)
      -			data.inputs[i].link = null;
      -
      -	if(data.outputs)
      -		for(var i = 0; i < data.outputs.length; ++i)
      -		{
      -			if(data.outputs[i].links)
      -				data.outputs[i].links.length = 0;
      -		}
      -
      -	delete data["id"];
      -	//remove links
      -	node.configure(data);
      -
      -	return node;
      -}
      -
      -
      -/**
      -* serialize and stringify
      -* @method toString
      -*/
      -
      -LGraphNode.prototype.toString = function()
      -{
      -	return JSON.stringify( this.serialize() );
      -}
      -//LGraphNode.prototype.unserialize = function(info) {} //this cannot be done from within, must be done in LiteGraph
      -
      -
      -/**
      -* get the title string
      -* @method getTitle
      -*/
      -
      -LGraphNode.prototype.getTitle = function()
      -{
      -	return this.title || this.constructor.title;
      -}
      -
      -
      -
      -// Execution *************************
      -/**
      -* sets the output data
      -* @method setOutputData
      -* @param {number} slot
      -* @param {*} data
      -*/
      -LGraphNode.prototype.setOutputData = function(slot, data)
      -{
      -	if(!this.outputs)
      -		return;
      -
      -	//this maybe slow and a niche case
      -	//if(slot && slot.constructor === String)
      -	//	slot = this.findOutputSlot(slot);
      -
      -	if(slot == -1 || slot >= this.outputs.length)
      -		return;
      -
      -	var output_info = this.outputs[slot];
      -	if(!output_info)
      -		return;
      -
      -	//store data in the output itself in case we want to debug
      -	output_info._data = data;
      -
      -	//if there are connections, pass the data to the connections
      -	if( this.outputs[slot].links )
      -	{
      -		for(var i = 0; i < this.outputs[slot].links.length; i++)
      -		{
      -			var link_id = this.outputs[slot].links[i];
      -			this.graph.links[ link_id ].data = data;
      -		}
      -	}
      -}
      -
      -/**
      -* 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
      -* @return {*} data or if it is not connected returns undefined
      -*/
      -LGraphNode.prototype.getInputData = function( slot, force_update )
      -{
      -	if(!this.inputs)
      -		return; //undefined;
      -
      -	if(slot >= this.inputs.length || this.inputs[slot].link == null)
      -		return;
      -
      -	var link_id = this.inputs[slot].link;
      -	var link = this.graph.links[ link_id ];
      -	if(!link) //bug: weird case but it happens sometimes
      -		return null;
      -
      -	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;
      -
      -	if(node.updateOutputData)
      -		node.updateOutputData( link.origin_slot );
      -	else if(node.onExecute)
      -		node.onExecute();
      -
      -	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
      -* @param {number} slot
      -* @return {boolean}
      -*/
      -LGraphNode.prototype.isInputConnected = function(slot)
      -{
      -	if(!this.inputs)
      -		return false;
      -	return (slot < this.inputs.length && this.inputs[slot].link != null);
      -}
      -
      -/**
      -* tells you info about an input connection (which node, type, etc)
      -* @method getInputInfo
      -* @param {number} slot
      -* @return {Object} object or null { link: id, name: string, type: string or 0 }
      -*/
      -LGraphNode.prototype.getInputInfo = function(slot)
      -{
      -	if(!this.inputs)
      -		return null;
      -	if(slot < this.inputs.length)
      -		return this.inputs[slot];
      -	return null;
      -}
      -
      -/**
      -* returns the node connected in the input slot
      -* @method getInputNode
      -* @param {number} slot
      -* @return {LGraphNode} node or null
      -*/
      -LGraphNode.prototype.getInputNode = function( slot )
      -{
      -	if(!this.inputs)
      -		return null;
      -	if(slot >= this.inputs.length)
      -		return null;
      -	var input = this.inputs[slot];
      -	if(!input || !input.link)
      -		return null;
      -	var link_info = this.graph.links[ input.link ];
      -	if(!link_info)
      -		return null;
      -	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
      -* @param {number} slot
      -* @return {Object}  object or null
      -*/
      -LGraphNode.prototype.getOutputData = function(slot)
      -{
      -	if(!this.outputs)
      -		return null;
      -	if(slot >= this.outputs.length)
      -		return null;
      -
      -	var info = this.outputs[slot];
      -	return info._data;
      -}
      -
      -
      -/**
      -* tells you info about an output connection (which node, type, etc)
      -* @method getOutputInfo
      -* @param {number} slot
      -* @return {Object}  object or null { name: string, type: string, links: [ ids of links in number ] }
      -*/
      -LGraphNode.prototype.getOutputInfo = function(slot)
      -{
      -	if(!this.outputs)
      -		return null;
      -	if(slot < this.outputs.length)
      -		return this.outputs[slot];
      -	return null;
      -}
      -
      -
      -/**
      -* tells you if there is a connection in one output slot
      -* @method isOutputConnected
      -* @param {number} slot
      -* @return {boolean}
      -*/
      -LGraphNode.prototype.isOutputConnected = function(slot)
      -{
      -	if(!this.outputs)
      -		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
      -* @param {number} slot
      -* @return {array}
      -*/
      -LGraphNode.prototype.getOutputNodes = function(slot)
      -{
      -	if(!this.outputs || this.outputs.length == 0)
      -		return null;
      -
      -	if(slot >= this.outputs.length)
      -		return null;
      -
      -	var output = this.outputs[slot];
      -	if(!output.links || output.links.length == 0)
      -		return null;
      -
      -	var r = [];
      -	for(var i = 0; i < output.links.length; i++)
      -	{
      -		var link_id = output.links[i];
      -		var link = this.graph.links[ link_id ];
      -		if(link)
      -		{
      -			var target_node = this.graph.getNodeById( link.target_id );
      -			if( target_node )
      -				r.push( target_node );
      -		}
      -	}
      -	return r;
      -}
      -
      -/**
      -* Triggers an event in this node, this will trigger any output with the same name
      -* @method trigger
      -* @param {String} event name ( "on_play", ... ) if action is equivalent to false then the event is send to all
      -* @param {*} param
      -*/
      -LGraphNode.prototype.trigger = function( action, param )
      -{
      -	if( !this.outputs || !this.outputs.length )
      -		return;
      -
      -	if(this.graph)
      -		this.graph._last_trigger_time = LiteGraph.getTime();
      -
      -	for(var i = 0; i < this.outputs.length; ++i)
      -	{
      -		var output = this.outputs[ i ];
      -		if(!output || output.type !== LiteGraph.EVENT || (action && output.name != action) )
      -			continue;
      -		this.triggerSlot( i, param );
      -	}
      -}
      -
      -/**
      -* Triggers an slot event in this node
      -* @method triggerSlot
      -* @param {Number} slot the index of the output slot
      -* @param {*} param
      -*/
      -LGraphNode.prototype.triggerSlot = function( slot, param )
      -{
      -	if( !this.outputs )
      -		return;
      -
      -	var output = this.outputs[ slot ];
      -	if( !output )
      -		return;
      -
      -	var links = output.links;
      -	if(!links || !links.length)
      -		return;
      -
      -	if(this.graph)
      -		this.graph._last_trigger_time = LiteGraph.getTime();
      -
      -	//for every link attached here
      -	for(var k = 0; k < links.length; ++k)
      -	{
      -		var link_info = this.graph.links[ links[k] ];
      -		if(!link_info) //not connected
      -			continue;
      -		var node = this.graph.getNodeById( link_info.target_id );
      -		if(!node) //node not found?
      -			continue;
      -
      -		//used to mark events in graph
      -		link_info._last_time = LiteGraph.getTime();
      -
      -		var target_connection = node.inputs[ link_info.target_slot ];
      -
      -		if(node.onAction)
      -			node.onAction( target_connection.name, param );
      -		else if(node.mode === LiteGraph.ON_TRIGGER)
      -		{
      -			if(node.onExecute)
      -				node.onExecute(param);
      -		}
      -	}
      -}
      -
      -/**
      -* add a new property to this node
      -* @method addProperty
      -* @param {string} name
      -* @param {*} default_value
      -* @param {string} type string defining the output type ("vec3","number",...)
      -* @param {Object} extra_info this can be used to have special properties of the property (like values, etc)
      -*/
      -LGraphNode.prototype.addProperty = function( name, default_value, type, extra_info )
      -{
      -	var o = { name: name, type: type, default_value: default_value };
      -	if(extra_info)
      -		for(var i in extra_info)
      -			o[i] = extra_info[i];
      -	if(!this.properties_info)
      -		this.properties_info = [];
      -	this.properties_info.push(o);
      -	if(!this.properties)
      -		this.properties = {};
      -	this.properties[ name ] = default_value;
      -	return o;
      -}
      -
      -
      -//connections
      -
      -/**
      -* add a new output slot to use in this node
      -* @method addOutput
      -* @param {string} name
      -* @param {string} type string defining the output type ("vec3","number",...)
      -* @param {Object} extra_info this can be used to have special properties of an output (label, special color, position, etc)
      -*/
      -LGraphNode.prototype.addOutput = function(name,type,extra_info)
      -{
      -	var o = { name: name, type: type, links: null };
      -	if(extra_info)
      -		for(var i in extra_info)
      -			o[i] = extra_info[i];
      -
      -	if(!this.outputs)
      -		this.outputs = [];
      -	this.outputs.push(o);
      -	if(this.onOutputAdded)
      -		this.onOutputAdded(o);
      -	this.size = this.computeSize();
      -	return o;
      -}
      -
      -/**
      -* add a new output slot to use in this node
      -* @method addOutputs
      -* @param {Array} array of triplets like [[name,type,extra_info],[...]]
      -*/
      -LGraphNode.prototype.addOutputs = function(array)
      -{
      -	for(var i = 0; i < array.length; ++i)
      -	{
      -		var info = array[i];
      -		var o = {name:info[0],type:info[1],link:null};
      -		if(array[2])
      -			for(var j in info[2])
      -				o[j] = info[2][j];
      -
      -		if(!this.outputs)
      -			this.outputs = [];
      -		this.outputs.push(o);
      -		if(this.onOutputAdded)
      -			this.onOutputAdded(o);
      -	}
      -
      -	this.size = this.computeSize();
      -}
      -
      -/**
      -* remove an existing output slot
      -* @method removeOutput
      -* @param {number} slot
      -*/
      -LGraphNode.prototype.removeOutput = function(slot)
      -{
      -	this.disconnectOutput(slot);
      -	this.outputs.splice(slot,1);
      -	this.size = this.computeSize();
      -	if(this.onOutputRemoved)
      -		this.onOutputRemoved(slot);
      -}
      -
      -/**
      -* add a new input slot to use in this node
      -* @method addInput
      -* @param {string} name
      -* @param {string} type string defining the input type ("vec3","number",...), it its a generic one use 0
      -* @param {Object} extra_info this can be used to have special properties of an input (label, color, position, etc)
      -*/
      -LGraphNode.prototype.addInput = function(name,type,extra_info)
      -{
      -	type = type || 0;
      -	var o = {name:name,type:type,link:null};
      -	if(extra_info)
      -		for(var i in extra_info)
      -			o[i] = extra_info[i];
      -
      -	if(!this.inputs)
      -		this.inputs = [];
      -	this.inputs.push(o);
      -	this.size = this.computeSize();
      -	if(this.onInputAdded)
      -		this.onInputAdded(o);
      -	return o;
      -}
      -
      -/**
      -* add several new input slots in this node
      -* @method addInputs
      -* @param {Array} array of triplets like [[name,type,extra_info],[...]]
      -*/
      -LGraphNode.prototype.addInputs = function(array)
      -{
      -	for(var i = 0; i < array.length; ++i)
      -	{
      -		var info = array[i];
      -		var o = {name:info[0], type:info[1], link:null};
      -		if(array[2])
      -			for(var j in info[2])
      -				o[j] = info[2][j];
      -
      -		if(!this.inputs)
      -			this.inputs = [];
      -		this.inputs.push(o);
      -		if(this.onInputAdded)
      -			this.onInputAdded(o);
      -	}
      -
      -	this.size = this.computeSize();
      -}
      -
      -/**
      -* remove an existing input slot
      -* @method removeInput
      -* @param {number} slot
      -*/
      -LGraphNode.prototype.removeInput = function(slot)
      -{
      -	this.disconnectInput(slot);
      -	this.inputs.splice(slot,1);
      -	this.size = this.computeSize();
      -	if(this.onInputRemoved)
      -		this.onInputRemoved(slot);
      -}
      -
      -/**
      -* add an special connection to this node (used for special kinds of graphs)
      -* @method addConnection
      -* @param {string} name
      -* @param {string} type string defining the input type ("vec3","number",...)
      -* @param {[x,y]} pos position of the connection inside the node
      -* @param {string} direction if is input or output
      -*/
      -LGraphNode.prototype.addConnection = function(name,type,pos,direction)
      -{
      -	var o = {
      -		name: name,
      -		type: type,
      -		pos: pos,
      -		direction: direction,
      -		links: null
      -	};
      -	this.connections.push( o );
      -	return o;
      -}
      -
      -/**
      -* computes the size of a node according to its inputs and output slots
      -* @method computeSize
      -* @param {number} minHeight
      -* @return {number} the total size
      -*/
      -LGraphNode.prototype.computeSize = function( minHeight, out )
      -{
      -	var rows = Math.max( this.inputs ? this.inputs.length : 1, this.outputs ? this.outputs.length : 1);
      -	var size = out || new Float32Array([0,0]);
      -	rows = Math.max(rows, 1);
      -	size[1] = rows * 14 + 6;
      -
      -	var font_size = 14;
      -	var title_width = compute_text_size( this.title );
      -	var input_width = 0;
      -	var output_width = 0;
      -
      -	if(this.inputs)
      -		for(var i = 0, l = this.inputs.length; i < l; ++i)
      -		{
      -			var input = this.inputs[i];
      -			var text = input.label || input.name || "";
      -			var text_width = compute_text_size( text );
      -			if(input_width < text_width)
      -				input_width = text_width;
      -		}
      -
      -	if(this.outputs)
      -		for(var i = 0, l = this.outputs.length; i < l; ++i)
      -		{
      -			var output = this.outputs[i];
      -			var text = output.label || output.name || "";
      -			var text_width = compute_text_size( text );
      -			if(output_width < text_width)
      -				output_width = text_width;
      -		}
      -
      -	size[0] = Math.max( input_width + output_width + 10, title_width );
      -	size[0] = Math.max( size[0], LiteGraph.NODE_WIDTH );
      -
      -	function compute_text_size( text )
      -	{
      -		if(!text)
      -			return 0;
      -		return font_size * text.length * 0.6;
      -	}
      -
      -	return size;
      -}
      -
      -/**
      -* returns the bounding of the object, used for rendering purposes
      -* bounding is: [topleft_cornerx, topleft_cornery, width, height]
      -* @method getBounding
      -* @return {Float32Array[4]} the total size
      -*/
      -LGraphNode.prototype.getBounding = function( out )
      -{
      -	out = out || new Float32Array(4);
      -	out[0] = this.pos[0] - 4;
      -	out[1] = this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT;
      -	out[2] = this.size[0] + 4;
      -	out[3] = this.size[1] + LiteGraph.NODE_TITLE_HEIGHT;
      -	return out;
      -}
      -
      -/**
      -* checks if a point is inside the shape of a node
      -* @method isPointInsideNode
      -* @param {number} x
      -* @param {number} y
      -* @return {boolean}
      -*/
      -LGraphNode.prototype.isPointInsideNode = function(x,y, margin)
      -{
      -	margin = margin || 0;
      -
      -	var margin_top = this.graph && this.graph.isLive() ? 0 : 20;
      -	if(this.flags.collapsed)
      -	{
      -		//if ( distance([x,y], [this.pos[0] + this.size[0]*0.5, this.pos[1] + this.size[1]*0.5]) < LiteGraph.NODE_COLLAPSED_RADIUS)
      -		if( isInsideRectangle( x, y, this.pos[0] - margin, this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT - margin, LiteGraph.NODE_COLLAPSED_WIDTH + 2 * margin, LiteGraph.NODE_TITLE_HEIGHT + 2 * margin ) )
      -			return true;
      -	}
      -	else if ( (this.pos[0] - 4 - margin) < x && (this.pos[0] + this.size[0] + 4 + margin) > x
      -		&& (this.pos[1] - margin_top - margin) < y && (this.pos[1] + this.size[1] + margin) > y)
      -		return true;
      -	return false;
      -}
      -
      -/**
      -* checks if a point is inside a node slot, and returns info about which slot
      -* @method getSlotInPosition
      -* @param {number} x
      -* @param {number} y
      -* @return {Object} if found the object contains { input|output: slot object, slot: number, link_pos: [x,y] }
      -*/
      -LGraphNode.prototype.getSlotInPosition = function( x, y )
      -{
      -	//search for inputs
      -	if(this.inputs)
      -		for(var i = 0, l = this.inputs.length; i < l; ++i)
      -		{
      -			var input = this.inputs[i];
      -			var link_pos = this.getConnectionPos( true,i );
      -			if( isInsideRectangle(x, y, link_pos[0] - 10, link_pos[1] - 5, 20,10) )
      -				return { input: input, slot: i, link_pos: link_pos, locked: input.locked };
      -		}
      -
      -	if(this.outputs)
      -		for(var i = 0, l = this.outputs.length; i < l; ++i)
      -		{
      -			var output = this.outputs[i];
      -			var link_pos = this.getConnectionPos(false,i);
      -			if( isInsideRectangle(x, y, link_pos[0] - 10, link_pos[1] - 5, 20,10) )
      -				return { output: output, slot: i, link_pos: link_pos, locked: output.locked };
      -		}
      -
      -	return null;
      -}
      -
      -/**
      -* returns the input slot with a given name (used for dynamic slots), -1 if not found
      -* @method findInputSlot
      -* @param {string} name the name of the slot
      -* @return {number} the slot (-1 if not found)
      -*/
      -LGraphNode.prototype.findInputSlot = function(name)
      -{
      -	if(!this.inputs)
      -		return -1;
      -	for(var i = 0, l = this.inputs.length; i < l; ++i)
      -		if(name == this.inputs[i].name)
      -			return i;
      -	return -1;
      -}
      -
      -/**
      -* returns the output slot with a given name (used for dynamic slots), -1 if not found
      -* @method findOutputSlot
      -* @param {string} name the name of the slot
      -* @return {number} the slot (-1 if not found)
      -*/
      -LGraphNode.prototype.findOutputSlot = function(name)
      -{
      -	if(!this.outputs) return -1;
      -	for(var i = 0, l = this.outputs.length; i < l; ++i)
      -		if(name == this.outputs[i].name)
      -			return i;
      -	return -1;
      -}
      -
      -/**
      -* connect this node output to the input of another node
      -* @method connect
      -* @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot)
      -* @param {LGraphNode} node the target node
      -* @param {number_or_string} target_slot the input slot of the target node (could be the number of the slot or the string with the name of the slot, or -1 to connect a trigger)
      -* @return {boolean} if it was connected succesfully
      -*/
      -LGraphNode.prototype.connect = function( slot, target_node, target_slot )
      -{
      -	target_slot = target_slot || 0;
      -
      -	//seek for the output slot
      -	if( slot.constructor === String )
      -	{
      -		slot = this.findOutputSlot(slot);
      -		if(slot == -1)
      -		{
      -			if(LiteGraph.debug)
      -				console.log("Connect: Error, no slot of name " + slot);
      -			return false;
      -		}
      -	}
      -	else if(!this.outputs || slot >= this.outputs.length)
      -	{
      -		if(LiteGraph.debug)
      -			console.log("Connect: Error, slot number not found");
      -		return false;
      -	}
      -
      -	if(target_node && target_node.constructor === Number)
      -		target_node = this.graph.getNodeById( target_node );
      -	if(!target_node)
      -		throw("Node not found");
      -
      -	//avoid loopback
      -	if(target_node == this)
      -		return false;
      -
      -	//you can specify the slot by name
      -	if(target_slot.constructor === String)
      -	{
      -		target_slot = target_node.findInputSlot( target_slot );
      -		if(target_slot == -1)
      -		{
      -			if(LiteGraph.debug)
      -				console.log("Connect: Error, no slot of name " + target_slot);
      -			return false;
      -		}
      -	}
      -	else if( target_slot === LiteGraph.EVENT )
      -	{
      -		//search for first slot with event?
      -		/*
      -		//create input for trigger
      -		var input = target_node.addInput("onTrigger", LiteGraph.EVENT );
      -		target_slot = target_node.inputs.length - 1; //last one is the one created
      -		target_node.mode = LiteGraph.ON_TRIGGER;
      -		*/
      -		return false;
      -	}
      -	else if( !target_node.inputs || target_slot >= target_node.inputs.length )
      -	{
      -		if(LiteGraph.debug)
      -			console.log("Connect: Error, slot number not found");
      -		return false;
      -	}
      -
      -	//if there is something already plugged there, disconnect
      -	if(target_node.inputs[ target_slot ].link != null )
      -		target_node.disconnectInput( target_slot );
      -
      -	//why here??
      -	this.setDirtyCanvas(false,true);
      -	this.graph.connectionChange( this );
      -
      -	var output = this.outputs[slot];
      -
      -	//allows nodes to block connection
      -	if(target_node.onConnectInput)
      -		if( target_node.onConnectInput( target_slot, output.type, output ) === false)
      -			return false;
      -
      -	var input = target_node.inputs[target_slot];
      -
      -	if( LiteGraph.isValidConnection( output.type, input.type ) )
      -	{
      -		var link_info = {
      -			id: this.graph.last_link_id++,
      -			type: input.type,
      -			origin_id: this.id,
      -			origin_slot: slot,
      -			target_id: target_node.id,
      -			target_slot: target_slot
      -		};
      -
      -		//add to graph links list
      -		this.graph.links[ link_info.id ] = link_info;
      -
      -		//connect in output
      -		if( output.links == null )
      -			output.links = [];
      -		output.links.push( link_info.id );
      -		//connect in input
      -		target_node.inputs[target_slot].link = link_info.id;
      -
      -		if(this.onConnectionsChange)
      -			this.onConnectionsChange( LiteGraph.OUTPUT, slot, true, link_info, output ); //link_info has been created now, so its updated
      -		if(target_node.onConnectionsChange)
      -			target_node.onConnectionsChange( LiteGraph.INPUT, target_slot, true, link_info, input );
      -	}
      -
      -	this.setDirtyCanvas(false,true);
      -	this.graph.connectionChange( this );
      -
      -	return true;
      -}
      -
      -/**
      -* disconnect one output to an specific node
      -* @method disconnectOutput
      -* @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot)
      -* @param {LGraphNode} target_node the target node to which this slot is connected [Optional, if not target_node is specified all nodes will be disconnected]
      -* @return {boolean} if it was disconnected succesfully
      -*/
      -LGraphNode.prototype.disconnectOutput = function( slot, target_node )
      -{
      -	if( slot.constructor === String )
      -	{
      -		slot = this.findOutputSlot(slot);
      -		if(slot == -1)
      -		{
      -			if(LiteGraph.debug)
      -				console.log("Connect: Error, no slot of name " + slot);
      -			return false;
      -		}
      -	}
      -	else if(!this.outputs || slot >= this.outputs.length)
      -	{
      -		if(LiteGraph.debug)
      -			console.log("Connect: Error, slot number not found");
      -		return false;
      -	}
      -
      -	//get output slot
      -	var output = this.outputs[slot];
      -	if(!output.links || output.links.length == 0)
      -		return false;
      -
      -	//one of the links
      -	if(target_node)
      -	{
      -		if(target_node.constructor === Number)
      -			target_node = this.graph.getNodeById( target_node );
      -		if(!target_node)
      -			throw("Target Node not found");
      -
      -		for(var i = 0, l = output.links.length; i < l; i++)
      -		{
      -			var link_id = output.links[i];
      -			var link_info = this.graph.links[ link_id ];
      -
      -			//is the link we are searching for...
      -			if( link_info.target_id == target_node.id )
      -			{
      -				output.links.splice(i,1); //remove here
      -				var input = target_node.inputs[ link_info.target_slot ];
      -				input.link = null; //remove there
      -				delete this.graph.links[ link_id ]; //remove the link from the links pool
      -				if(target_node.onConnectionsChange)
      -					target_node.onConnectionsChange( LiteGraph.INPUT, link_info.target_slot, false, link_info, input ); //link_info hasnt been modified so its ok
      -				if(this.onConnectionsChange)
      -					this.onConnectionsChange( LiteGraph.OUTPUT, slot, false, link_info, output );
      -				break;
      -			}
      -		}
      -	}
      -	else //all the links
      -	{
      -		for(var i = 0, l = output.links.length; i < l; i++)
      -		{
      -			var link_id = output.links[i];
      -			var link_info = this.graph.links[ link_id ];
      -			if(!link_info) //bug: it happens sometimes
      -				continue;
      -
      -			var target_node = this.graph.getNodeById( link_info.target_id );
      -			var input = null;
      -			if(target_node)
      -			{
      -				input = target_node.inputs[ link_info.target_slot ];
      -				input.link = null; //remove other side link
      -				if(target_node.onConnectionsChange)
      -					target_node.onConnectionsChange( LiteGraph.INPUT, link_info.target_slot, false, link_info, input ); //link_info hasnt been modified so its ok
      -			}
      -			delete this.graph.links[ link_id ]; //remove the link from the links pool
      -			if(this.onConnectionsChange)
      -				this.onConnectionsChange( LiteGraph.OUTPUT, slot, false, link_info, output );
      -		}
      -		output.links = null;
      -	}
      -
      -
      -	this.setDirtyCanvas(false,true);
      -	this.graph.connectionChange( this );
      -	return true;
      -}
      -
      -/**
      -* disconnect one input
      -* @method disconnectInput
      -* @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot)
      -* @return {boolean} if it was disconnected succesfully
      -*/
      -LGraphNode.prototype.disconnectInput = function( slot )
      -{
      -	//seek for the output slot
      -	if( slot.constructor === String )
      -	{
      -		slot = this.findInputSlot(slot);
      -		if(slot == -1)
      -		{
      -			if(LiteGraph.debug)
      -				console.log("Connect: Error, no slot of name " + slot);
      -			return false;
      -		}
      -	}
      -	else if(!this.inputs || slot >= this.inputs.length)
      -	{
      -		if(LiteGraph.debug)
      -			console.log("Connect: Error, slot number not found");
      -		return false;
      -	}
      -
      -	var input = this.inputs[slot];
      -	if(!input)
      -		return false;
      -
      -	var link_id = this.inputs[slot].link;
      -	this.inputs[slot].link = null;
      -
      -	//remove other side
      -	var link_info = this.graph.links[ link_id ];
      -	if( link_info )
      -	{
      -		var target_node = this.graph.getNodeById( link_info.origin_id );
      -		if(!target_node)
      -			return false;
      -
      -		var output = target_node.outputs[ link_info.origin_slot ];
      -		if(!output || !output.links || output.links.length == 0)
      -			return false;
      -
      -		//search in the inputs list for this link
      -		for(var i = 0, l = output.links.length; i < l; i++)
      -		{
      -			if( output.links[i] == link_id )
      -			{
      -				output.links.splice(i,1);
      -				break;
      -			}
      -		}
      -
      -		delete this.graph.links[ link_id ]; //remove from the pool
      -
      -		if( this.onConnectionsChange )
      -			this.onConnectionsChange( LiteGraph.INPUT, slot, false, link_info, input );
      -		if( target_node.onConnectionsChange )
      -			target_node.onConnectionsChange( LiteGraph.OUTPUT, i, false, link_info, output );
      -	}
      -
      -	this.setDirtyCanvas(false,true);
      -	this.graph.connectionChange( this );
      -	return true;
      -}
      -
      -/**
      -* returns the center of a connection point in canvas coords
      -* @method getConnectionPos
      -* @param {boolean} is_input true if if a input slot, false if it is an output
      -* @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot)
      -* @return {[x,y]} the position
      -**/
      -LGraphNode.prototype.getConnectionPos = function(is_input, slot_number)
      -{
      -	if(this.flags.collapsed)
      -	{
      -		if(is_input)
      -			return [this.pos[0], this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT * 0.5];
      -		else
      -			return [this.pos[0] + LiteGraph.NODE_COLLAPSED_WIDTH, this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT * 0.5];
      -		//return [this.pos[0] + this.size[0] * 0.5, this.pos[1] + this.size[1] * 0.5];
      -	}
      -
      -	if(is_input && slot_number == -1)
      -	{
      -		return [this.pos[0] + 10, this.pos[1] + 10];
      -	}
      -
      -	if(is_input && this.inputs.length > slot_number && this.inputs[slot_number].pos)
      -		return [this.pos[0] + this.inputs[slot_number].pos[0],this.pos[1] + this.inputs[slot_number].pos[1]];
      -	else if(!is_input && this.outputs.length > slot_number && this.outputs[slot_number].pos)
      -		return [this.pos[0] + this.outputs[slot_number].pos[0],this.pos[1] + this.outputs[slot_number].pos[1]];
      -
      -	if(!is_input) //output
      -		return [this.pos[0] + this.size[0] + 1, this.pos[1] + 10 + slot_number * LiteGraph.NODE_SLOT_HEIGHT];
      -	return [this.pos[0] , this.pos[1] + 10 + slot_number * LiteGraph.NODE_SLOT_HEIGHT];
      -}
      -
      -/* Force align to grid */
      -LGraphNode.prototype.alignToGrid = function()
      -{
      -	this.pos[0] = LiteGraph.CANVAS_GRID_SIZE * Math.round(this.pos[0] / LiteGraph.CANVAS_GRID_SIZE);
      -	this.pos[1] = LiteGraph.CANVAS_GRID_SIZE * Math.round(this.pos[1] / LiteGraph.CANVAS_GRID_SIZE);
      -}
      -
      -
      -/* Console output */
      -LGraphNode.prototype.trace = function(msg)
      -{
      -	if(!this.console)
      -		this.console = [];
      -	this.console.push(msg);
      -	if(this.console.length > LGraphNode.MAX_CONSOLE)
      -		this.console.shift();
      -
      -	this.graph.onNodeTrace(this,msg);
      -}
      -
      -/* Forces to redraw or the main canvas (LGraphNode) or the bg canvas (links) */
      -LGraphNode.prototype.setDirtyCanvas = function(dirty_foreground, dirty_background)
      -{
      -	if(!this.graph)
      -		return;
      -	this.graph.sendActionToCanvas("setDirty",[dirty_foreground, dirty_background]);
      -}
      -
      -LGraphNode.prototype.loadImage = function(url)
      -{
      -	var img = new Image();
      -	img.src = LiteGraph.node_images_path + url;
      -	img.ready = false;
      -
      -	var that = this;
      -	img.onload = function() {
      -		this.ready = true;
      -		that.setDirtyCanvas(true);
      -	}
      -	return img;
      -}
      -
      -//safe LGraphNode action execution (not sure if safe)
      -/*
      -LGraphNode.prototype.executeAction = function(action)
      -{
      -	if(action == "") return false;
      -
      -	if( action.indexOf(";") != -1 || action.indexOf("}") != -1)
      -	{
      -		this.trace("Error: Action contains unsafe characters");
      -		return false;
      -	}
      -
      -	var tokens = action.split("(");
      -	var func_name = tokens[0];
      -	if( typeof(this[func_name]) != "function")
      -	{
      -		this.trace("Error: Action not found on node: " + func_name);
      -		return false;
      -	}
      -
      -	var code = action;
      -
      -	try
      -	{
      -		var _foo = eval;
      -		eval = null;
      -		(new Function("with(this) { " + code + "}")).call(this);
      -		eval = _foo;
      -	}
      -	catch (err)
      -	{
      -		this.trace("Error executing action {" + action + "} :" + err);
      -		return false;
      -	}
      -
      -	return true;
      -}
      -*/
      -
      -/* Allows to get onMouseMove and onMouseUp events even if the mouse is out of focus */
      -LGraphNode.prototype.captureInput = function(v)
      -{
      -	if(!this.graph || !this.graph.list_of_graphcanvas)
      -		return;
      -
      -	var list = this.graph.list_of_graphcanvas;
      -
      -	for(var i = 0; i < list.length; ++i)
      -	{
      -		var c = list[i];
      -		//releasing somebody elses capture?!
      -		if(!v && c.node_capturing_input != this)
      -			continue;
      -
      -		//change
      -		c.node_capturing_input = v ? this : null;
      -	}
      -}
      -
      -/**
      -* Collapse the node to make it smaller on the canvas
      -* @method collapse
      -**/
      -LGraphNode.prototype.collapse = function()
      -{
      -	if(!this.flags.collapsed)
      -		this.flags.collapsed = true;
      -	else
      -		this.flags.collapsed = false;
      -	this.setDirtyCanvas(true,true);
      -}
      -
      -/**
      -* Forces the node to do not move or realign on Z
      -* @method pin
      -**/
      -
      -LGraphNode.prototype.pin = function(v)
      -{
      -	if(v === undefined)
      -		this.flags.pinned = !this.flags.pinned;
      -	else
      -		this.flags.pinned = v;
      -}
      -
      -LGraphNode.prototype.localToScreen = function(x,y, graphcanvas)
      -{
      -	return [(x + this.pos[0]) * graphcanvas.scale + graphcanvas.offset[0],
      -		(y + this.pos[1]) * graphcanvas.scale + graphcanvas.offset[1]];
      -}
      -
      -
      -
      -//*********************************************************************************
      -// LGraphCanvas: LGraph renderer CLASS
      -//*********************************************************************************
      -
      -/**
      -* The Global Scope. It contains all the registered node classes.
      -* Valid callbacks are: onNodeSelected, onNodeDeselected, onShowNodePanel, onNodeDblClicked
      -*
      -* @class LGraphCanvas
      -* @constructor
      -* @param {HTMLCanvas} canvas the canvas where you want to render (it accepts a selector in string format or the canvas element itself)
      -* @param {LGraph} graph [optional]
      -* @param {Object} options [optional] { skip_rendering, autoresize }
      -*/
      -function LGraphCanvas( canvas, graph, options )
      -{
      -	options = options || {};
      -
      -	//if(graph === undefined)
      -  //	throw ("No graph assigned");
      -	this.background_image = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQBJREFUeNrs1rEKwjAUhlETUkj3vP9rdmr1Ysammk2w5wdxuLgcMHyptfawuZX4pJSWZTnfnu/lnIe/jNNxHHGNn//HNbbv+4dr6V+11uF527arU7+u63qfa/bnmh8sWLBgwYJlqRf8MEptXPBXJXa37BSl3ixYsGDBMliwFLyCV/DeLIMFCxYsWLBMwSt4Be/NggXLYMGCBUvBK3iNruC9WbBgwYJlsGApeAWv4L1ZBgsWLFiwYJmCV/AK3psFC5bBggULloJX8BpdwXuzYMGCBctgwVLwCl7Be7MMFixYsGDBsu8FH1FaSmExVfAxBa/gvVmwYMGCZbBg/W4vAQYA5tRF9QYlv/QAAAAASUVORK5CYII='
      -
      -	if(canvas && canvas.constructor === String )
      -		canvas = document.querySelector( canvas );
      -
      -	this.max_zoom = 10;
      -	this.min_zoom = 0.1;
      -
      -	this.title_text_font = "bold 14px Arial";
      -	this.inner_text_font = "normal 12px Arial";
      -	this.default_link_color = "#AAC";
      -	this.default_connection_color = {
      -		input_off: "#AAB",
      -		input_on: "#7F7",
      -		output_off: "#AAB",
      -		output_on: "#7F7"
      -	};
      -
      -	this.highquality_render = true;
      -	this.editor_alpha = 1; //used for transition
      -	this.pause_rendering = false;
      -	this.render_shadows = true;
      -	this.clear_background = true;
      -
      -	this.render_only_selected = true;
      -	this.live_mode = false;
      -	this.show_info = true;
      -	this.allow_dragcanvas = true;
      -	this.allow_dragnodes = true;
      -	this.allow_interaction = true; //allow to control widgets, buttons, collapse, etc
      -	this.drag_mode = false;
      -	this.dragging_rectangle = null;
      -
      -	this.always_render_background = false;
      -	this.render_canvas_area = true;
      -	this.render_connections_shadows = false; //too much cpu
      -	this.render_connections_border = true;
      -	this.render_curved_connections = true;
      -	this.render_connection_arrows = true;
      -
      -	this.connections_width = 3;
      -
      -	//link canvas and graph
      -	if(graph)
      -		graph.attachCanvas(this);
      -
      -	this.setCanvas( canvas );
      -	this.clear();
      -
      -	if(!options.skip_render)
      -		this.startRendering();
      -
      -	this.autoresize = options.autoresize;
      -}
      -
      -global.LGraphCanvas = LiteGraph.LGraphCanvas = LGraphCanvas;
      -
      -LGraphCanvas.link_type_colors = {"-1":"#F85",'number':"#AAC","node":"#DCA"};
      -
      -
      -/**
      -* clears all the data inside
      -*
      -* @method clear
      -*/
      -LGraphCanvas.prototype.clear = function()
      -{
      -	this.frame = 0;
      -	this.last_draw_time = 0;
      -	this.render_time = 0;
      -	this.fps = 0;
      -
      -	this.scale = 1;
      -	this.offset = [0,0];
      -
      -	this.dragging_rectangle = null;
      -
      -	this.selected_nodes = {};
      -	this.visible_nodes = [];
      -	this.node_dragged = null;
      -	this.node_over = null;
      -	this.node_capturing_input = null;
      -	this.connecting_node = null;
      -	this.highlighted_links = {};
      -
      -	this.dirty_canvas = true;
      -	this.dirty_bgcanvas = true;
      -	this.dirty_area = null;
      -
      -	this.node_in_panel = null;
      -
      -	this.last_mouse = [0,0];
      -	this.last_mouseclick = 0;
      -
      -	if(this.onClear)
      -		this.onClear();
      -	//this.UIinit();
      -}
      -
      -/**
      -* assigns a graph, you can reasign graphs to the same canvas
      -*
      -* @method setGraph
      -* @param {LGraph} graph
      -*/
      -LGraphCanvas.prototype.setGraph = function( graph, skip_clear )
      -{
      -	if(this.graph == graph)
      -		return;
      -
      -	if(!skip_clear)
      -		this.clear();
      -
      -	if(!graph && this.graph)
      -	{
      -		this.graph.detachCanvas(this);
      -		return;
      -	}
      -
      -	/*
      -	if(this.graph)
      -		this.graph.canvas = null; //remove old graph link to the canvas
      -	this.graph = graph;
      -	if(this.graph)
      -		this.graph.canvas = this;
      -	*/
      -	graph.attachCanvas(this);
      -	this.setDirty(true,true);
      -}
      -
      -/**
      -* opens a graph contained inside a node in the current graph
      -*
      -* @method openSubgraph
      -* @param {LGraph} graph
      -*/
      -LGraphCanvas.prototype.openSubgraph = function(graph)
      -{
      -	if(!graph)
      -		throw("graph cannot be null");
      -
      -	if(this.graph == graph)
      -		throw("graph cannot be the same");
      -
      -	this.clear();
      -
      -	if(this.graph)
      -	{
      -		if(!this._graph_stack)
      -			this._graph_stack = [];
      -		this._graph_stack.push(this.graph);
      -	}
      -
      -	graph.attachCanvas(this);
      -	this.setDirty(true,true);
      -}
      -
      -/**
      -* closes a subgraph contained inside a node
      -*
      -* @method closeSubgraph
      -* @param {LGraph} assigns a graph
      -*/
      -LGraphCanvas.prototype.closeSubgraph = function()
      -{
      -	if(!this._graph_stack || this._graph_stack.length == 0)
      -		return;
      -	var graph = this._graph_stack.pop();
      -	this.selected_nodes = {};
      -	this.highlighted_links = {};
      -	graph.attachCanvas(this);
      -	this.setDirty(true,true);
      -}
      -
      -/**
      -* assigns a canvas
      -*
      -* @method setCanvas
      -* @param {Canvas} assigns a canvas (also accepts the ID of the element (not a selector)
      -*/
      -LGraphCanvas.prototype.setCanvas = function( canvas, skip_events )
      -{
      -	var that = this;
      -
      -	if(canvas)
      -	{
      -		if( canvas.constructor === String )
      -		{
      -			canvas = document.getElementById(canvas);
      -			if(!canvas)
      -				throw("Error creating LiteGraph canvas: Canvas not found");
      -		}
      -	}
      -
      -	if(canvas === this.canvas)
      -		return;
      -
      -	if(!canvas && this.canvas)
      -	{
      -		//maybe detach events from old_canvas
      -		if(!skip_events)
      -			this.unbindEvents();
      -	}
      -
      -	this.canvas = canvas;
      -
      -	if(!canvas)
      -		return;
      -
      -	//this.canvas.tabindex = "1000";
      -	canvas.className += " lgraphcanvas";
      -	canvas.data = this;
      -
      -	//bg canvas: used for non changing stuff
      -	this.bgcanvas = null;
      -	if(!this.bgcanvas)
      -	{
      -		this.bgcanvas = document.createElement("canvas");
      -		this.bgcanvas.width = this.canvas.width;
      -		this.bgcanvas.height = this.canvas.height;
      -	}
      -
      -	if(canvas.getContext == null)
      -	{
      -		if( canvas.localName != "canvas" )
      -			throw("Element supplied for LGraphCanvas must be a <canvas> element, you passed a " + canvas.localName );
      -		throw("This browser doesnt support Canvas");
      -	}
      -
      -	var ctx = this.ctx = canvas.getContext("2d");
      -	if(ctx == null)
      -	{
      -		console.warn("This canvas seems to be WebGL, enabling WebGL renderer");
      -		this.enableWebGL();
      -	}
      -
      -	//input:  (move and up could be unbinded)
      -	this._mousemove_callback = this.processMouseMove.bind(this);
      -	this._mouseup_callback = this.processMouseUp.bind(this);
      -
      -	if(!skip_events)
      -		this.bindEvents();
      -}
      -
      -//used in some events to capture them
      -LGraphCanvas.prototype._doNothing = function doNothing(e) { e.preventDefault(); return false; };
      -LGraphCanvas.prototype._doReturnTrue = function doNothing(e) { e.preventDefault(); return true; };
      -
      -LGraphCanvas.prototype.bindEvents = function()
      -{
      -	if(	this._events_binded )
      -	{
      -		console.warn("LGraphCanvas: events already binded");
      -		return;
      -	}
      -
      -	var canvas = this.canvas;
      -	var ref_window = this.getCanvasWindow();
      -	var document = ref_window.document; //hack used when moving canvas between windows
      -
      -	this._mousedown_callback = this.processMouseDown.bind(this);
      -	this._mousewheel_callback = this.processMouseWheel.bind(this);
      -
      -	canvas.addEventListener("mousedown", this._mousedown_callback, true ); //down do not need to store the binded
      -	canvas.addEventListener("mousemove", this._mousemove_callback );
      -	canvas.addEventListener("mousewheel", this._mousewheel_callback, false);
      -
      -	canvas.addEventListener("contextmenu", this._doNothing );
      -	canvas.addEventListener("DOMMouseScroll", this._mousewheel_callback, false);
      -
      -	//touch events
      -	//if( 'touchstart' in document.documentElement )
      -	{
      -		canvas.addEventListener("touchstart", this.touchHandler, true);
      -		canvas.addEventListener("touchmove", this.touchHandler, true);
      -		canvas.addEventListener("touchend", this.touchHandler, true);
      -		canvas.addEventListener("touchcancel", this.touchHandler, true);
      -	}
      -
      -	//Keyboard ******************
      -	this._key_callback = this.processKey.bind(this);
      -
      -	canvas.addEventListener("keydown", this._key_callback, true );
      -	document.addEventListener("keyup", this._key_callback, true ); //in document, otherwise it doesnt fire keyup
      -
      -	//Droping Stuff over nodes ************************************
      -	this._ondrop_callback = this.processDrop.bind(this);
      -
      -	canvas.addEventListener("dragover", this._doNothing, false );
      -	canvas.addEventListener("dragend", this._doNothing, false );
      -	canvas.addEventListener("drop", this._ondrop_callback, false );
      -	canvas.addEventListener("dragenter", this._doReturnTrue, false );
      -
      -	this._events_binded = true;
      -}
      -
      -LGraphCanvas.prototype.unbindEvents = function()
      -{
      -	if(	!this._events_binded )
      -	{
      -		console.warn("LGraphCanvas: no events binded");
      -		return;
      -	}
      -
      -	var ref_window = this.getCanvasWindow();
      -	var document = ref_window.document;
      -
      -	this.canvas.removeEventListener( "mousedown", this._mousedown_callback );
      -	this.canvas.removeEventListener( "mousewheel", this._mousewheel_callback );
      -	this.canvas.removeEventListener( "DOMMouseScroll", this._mousewheel_callback );
      -	this.canvas.removeEventListener( "keydown", this._key_callback );
      -	document.removeEventListener( "keyup", this._key_callback );
      -	this.canvas.removeEventListener( "contextmenu", this._doNothing );
      -	this.canvas.removeEventListener( "drop", this._ondrop_callback );
      -	this.canvas.removeEventListener( "dragenter", this._doReturnTrue );
      -
      -	this.canvas.removeEventListener("touchstart", this.touchHandler );
      -	this.canvas.removeEventListener("touchmove", this.touchHandler );
      -	this.canvas.removeEventListener("touchend", this.touchHandler );
      -	this.canvas.removeEventListener("touchcancel", this.touchHandler );
      -
      -	this._mousedown_callback = null;
      -	this._mousewheel_callback = null;
      -	this._key_callback = null;
      -	this._ondrop_callback = null;
      -
      -	this._events_binded = false;
      -}
      -
      -LGraphCanvas.getFileExtension = function (url)
      -{
      -	var question = url.indexOf("?");
      -	if(question != -1)
      -		url = url.substr(0,question);
      -	var point = url.lastIndexOf(".");
      -	if(point == -1)
      -		return "";
      -	return url.substr(point+1).toLowerCase();
      -}
      -
      -//this file allows to render the canvas using WebGL instead of Canvas2D
      -//this is useful if you plant to render 3D objects inside your nodes
      -LGraphCanvas.prototype.enableWebGL = function()
      -{
      -	if(typeof(GL) === undefined)
      -		throw("litegl.js must be included to use a WebGL canvas");
      -	if(typeof(enableWebGLCanvas) === undefined)
      -		throw("webglCanvas.js must be included to use this feature");
      -
      -	this.gl = this.ctx = enableWebGLCanvas(this.canvas);
      -	this.ctx.webgl = true;
      -	this.bgcanvas = this.canvas;
      -	this.bgctx = this.gl;
      -
      -	/*
      -	GL.create({ canvas: this.bgcanvas });
      -	this.bgctx = enableWebGLCanvas( this.bgcanvas );
      -	window.gl = this.gl;
      -	*/
      -}
      -
      -
      -/*
      -LGraphCanvas.prototype.UIinit = function()
      -{
      -	var that = this;
      -	$("#node-console input").change(function(e)
      -	{
      -		if(e.target.value == "")
      -			return;
      -
      -		var node = that.node_in_panel;
      -		if(!node)
      -			return;
      -
      -		node.trace("] " + e.target.value, "#333");
      -		if(node.onConsoleCommand)
      -		{
      -			if(!node.onConsoleCommand(e.target.value))
      -				node.trace("command not found", "#A33");
      -		}
      -		else if (e.target.value == "info")
      -		{
      -			node.trace("Special methods:");
      -			for(var i in node)
      -			{
      -				if(typeof(node[i]) == "function" && LGraphNode.prototype[i] == null && i.substr(0,2) != "on" && i[0] != "_")
      -					node.trace(" + " + i);
      -			}
      -		}
      -		else
      -		{
      -			try
      -			{
      -				eval("var _foo = function() { return ("+e.target.value+"); }");
      -				var result = _foo.call(node);
      -				if(result)
      -					node.trace(result.toString());
      -				delete window._foo;
      -			}
      -			catch(err)
      -			{
      -				node.trace("error: " + err, "#A33");
      -			}
      -		}
      -
      -		this.value = "";
      -	});
      -}
      -*/
      -
      -/**
      -* marks as dirty the canvas, this way it will be rendered again
      -*
      -* @class LGraphCanvas
      -* @method setDirty
      -* @param {bool} fgcanvas if the foreground canvas is dirty (the one containing the nodes)
      -* @param {bool} bgcanvas if the background canvas is dirty (the one containing the wires)
      -*/
      -LGraphCanvas.prototype.setDirty = function(fgcanvas,bgcanvas)
      -{
      -	if(fgcanvas)
      -		this.dirty_canvas = true;
      -	if(bgcanvas)
      -		this.dirty_bgcanvas = true;
      -}
      -
      -/**
      -* Used to attach the canvas in a popup
      -*
      -* @method getCanvasWindow
      -* @return {window} returns the window where the canvas is attached (the DOM root node)
      -*/
      -LGraphCanvas.prototype.getCanvasWindow = function()
      -{
      -	if(!this.canvas)
      -		return window;
      -	var doc = this.canvas.ownerDocument;
      -	return doc.defaultView || doc.parentWindow;
      -}
      -
      -/**
      -* starts rendering the content of the canvas when needed
      -*
      -* @method startRendering
      -*/
      -LGraphCanvas.prototype.startRendering = function()
      -{
      -	if(this.is_rendering)
      -		return; //already rendering
      -
      -	this.is_rendering = true;
      -	renderFrame.call(this);
      -
      -	function renderFrame()
      -	{
      -		if(!this.pause_rendering)
      -			this.draw();
      -
      -		var window = this.getCanvasWindow();
      -		if(this.is_rendering)
      -			window.requestAnimationFrame( renderFrame.bind(this) );
      -	}
      -}
      -
      -/**
      -* stops rendering the content of the canvas (to save resources)
      -*
      -* @method stopRendering
      -*/
      -LGraphCanvas.prototype.stopRendering = function()
      -{
      -	this.is_rendering = false;
      -	/*
      -	if(this.rendering_timer_id)
      -	{
      -		clearInterval(this.rendering_timer_id);
      -		this.rendering_timer_id = null;
      -	}
      -	*/
      -}
      -
      -/* LiteGraphCanvas input */
      -
      -LGraphCanvas.prototype.processMouseDown = function(e)
      -{
      -	if(!this.graph)
      -		return;
      -
      -	this.adjustMouseEvent(e);
      -
      -	var ref_window = this.getCanvasWindow();
      -	var document = ref_window.document;
      -	LGraphCanvas.active_canvas = this;
      -
      -	//move mouse move event to the window in case it drags outside of the canvas
      -	this.canvas.removeEventListener("mousemove", this._mousemove_callback );
      -	ref_window.document.addEventListener("mousemove", this._mousemove_callback, true ); //catch for the entire window
      -	ref_window.document.addEventListener("mouseup", this._mouseup_callback, true );
      -
      -	var n = this.graph.getNodeOnPos( e.canvasX, e.canvasY, this.visible_nodes );
      -	var skip_dragging = false;
      -	var skip_action = false;
      -
      -    LiteGraph.closeAllContextMenus( ref_window );
      -
      -	if(e.which == 1) //left button mouse
      -	{
      -		if( e.ctrlKey )
      -		{
      -			this.dragging_rectangle = new Float32Array(4);
      -			this.dragging_rectangle[0] = e.canvasX;
      -			this.dragging_rectangle[1] = e.canvasY;
      -			this.dragging_rectangle[2] = 1;
      -			this.dragging_rectangle[3] = 1;
      -			skip_action = true;
      -		}
      -
      -		var clicking_canvas_bg = false;
      -
      -		//when clicked on top of a node
      -		//and it is not interactive
      -		if( n && this.allow_interaction && !skip_action )
      -		{
      -			if(!this.live_mode && !n.flags.pinned)
      -				this.bringToFront(n); //if it wasnt selected?
      -
      -			//not dragging mouse to connect two slots
      -			if(!this.connecting_node && !n.flags.collapsed && !this.live_mode)
      -			{
      -				//search for outputs
      -				if(n.outputs)
      -					for(var i = 0, l = n.outputs.length; i < l; ++i)
      -					{
      -						var output = n.outputs[i];
      -						var link_pos = n.getConnectionPos(false,i);
      -						if( isInsideRectangle(e.canvasX, e.canvasY, link_pos[0] - 10, link_pos[1] - 5, 20,10) )
      -						{
      -							this.connecting_node = n;
      -							this.connecting_output = output;
      -							this.connecting_pos = n.getConnectionPos(false,i);
      -							this.connecting_slot = i;
      -
      -							skip_action = true;
      -							break;
      -						}
      -					}
      -
      -				//search for inputs
      -				if(n.inputs)
      -					for(var i = 0, l = n.inputs.length; i < l; ++i)
      -					{
      -						var input = n.inputs[i];
      -						var link_pos = n.getConnectionPos(true,i);
      -						if( isInsideRectangle(e.canvasX, e.canvasY, link_pos[0] - 10, link_pos[1] - 5, 20,10) )
      -						{
      -							if(input.link !== null)
      -							{
      -								n.disconnectInput(i);
      -								this.dirty_bgcanvas = true;
      -								skip_action = true;
      -							}
      -						}
      -					}
      -
      -				//Search for corner
      -				if( !skip_action && isInsideRectangle(e.canvasX, e.canvasY, n.pos[0] + n.size[0] - 5, n.pos[1] + n.size[1] - 5 ,5,5 ))
      -				{
      -					this.resizing_node = n;
      -					this.canvas.style.cursor = "se-resize";
      -					skip_action = true;
      -				}
      -			}
      -
      -			//Search for corner
      -			if( !skip_action && isInsideRectangle(e.canvasX, e.canvasY, n.pos[0], n.pos[1] - LiteGraph.NODE_TITLE_HEIGHT ,LiteGraph.NODE_TITLE_HEIGHT, LiteGraph.NODE_TITLE_HEIGHT ))
      -			{
      -				n.collapse();
      -				skip_action = true;
      -			}
      -
      -			//it wasnt clicked on the links boxes
      -			if(!skip_action)
      -			{
      -				var block_drag_node = false;
      -
      -				//double clicking
      -				var now = LiteGraph.getTime();
      -				if ((now - this.last_mouseclick) < 300 && this.selected_nodes[n.id])
      -				{
      -					//double click node
      -					if( n.onDblClick)
      -						n.onDblClick(e);
      -					this.processNodeDblClicked(n);
      -					block_drag_node = true;
      -				}
      -
      -				//if do not capture mouse
      -
      -				if( n.onMouseDown && n.onMouseDown(e, [e.canvasX - n.pos[0], e.canvasY - n.pos[1]] ) )
      -					block_drag_node = true;
      -				else if(this.live_mode)
      -				{
      -					clicking_canvas_bg = true;
      -					block_drag_node = true;
      -				}
      -
      -				if(!block_drag_node)
      -				{
      -					if(this.allow_dragnodes)
      -						this.node_dragged = n;
      -
      -					if(!this.selected_nodes[n.id])
      -						this.processNodeSelected(n,e);
      -				}
      -
      -				this.dirty_canvas = true;
      -			}
      -		}
      -		else
      -			clicking_canvas_bg = true;
      -
      -		if(!skip_action && clicking_canvas_bg && this.allow_dragcanvas)
      -		{
      -			this.dragging_canvas = true;
      -		}
      -	}
      -	else if (e.which == 2) //middle button
      -	{
      -
      -	}
      -	else if (e.which == 3) //right button
      -	{
      -		this.processContextMenu(n,e);
      -	}
      -
      -	//TODO
      -	//if(this.node_selected != prev_selected)
      -	//	this.onNodeSelectionChange(this.node_selected);
      -
      -	this.last_mouse[0] = e.localX;
      -	this.last_mouse[1] = e.localY;
      -	this.last_mouseclick = LiteGraph.getTime();
      -	this.canvas_mouse = [e.canvasX, e.canvasY];
      -
      -	/*
      -	if( (this.dirty_canvas || this.dirty_bgcanvas) && this.rendering_timer_id == null)
      -		this.draw();
      -	*/
      -
      -	this.graph.change();
      -
      -	//this is to ensure to defocus(blur) if a text input element is on focus
      -	if(!ref_window.document.activeElement || (ref_window.document.activeElement.nodeName.toLowerCase() != "input" && ref_window.document.activeElement.nodeName.toLowerCase() != "textarea"))
      -		e.preventDefault();
      -	e.stopPropagation();
      -
      -	if(this.onMouseDown)
      -		this.onMouseDown(e);
      -
      -	return false;
      -}
      -
      -LGraphCanvas.prototype.processMouseMove = function(e)
      -{
      -	if(this.autoresize)
      -		this.resize();
      -
      -	if(!this.graph)
      -		return;
      -
      -	LGraphCanvas.active_canvas = this;
      -	this.adjustMouseEvent(e);
      -	var mouse = [e.localX, e.localY];
      -	var delta = [mouse[0] - this.last_mouse[0], mouse[1] - this.last_mouse[1]];
      -	this.last_mouse = mouse;
      -	this.canvas_mouse = [e.canvasX, e.canvasY];
      -
      -	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.dragging_canvas)
      -	{
      -		this.offset[0] += delta[0] / this.scale;
      -		this.offset[1] += delta[1] / this.scale;
      -		this.dirty_canvas = true;
      -		this.dirty_bgcanvas = true;
      -	}
      -	else if(this.allow_interaction)
      -	{
      -		if(this.connecting_node)
      -			this.dirty_canvas = true;
      -
      -		//get node over
      -		var n = 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 && n != this.graph._nodes[i])
      -			{
      -				//mouse leave
      -				this.graph._nodes[i].mouseOver = false;
      -				if(this.node_over && this.node_over.onMouseLeave)
      -					this.node_over.onMouseLeave(e);
      -				this.node_over = null;
      -				this.dirty_canvas = true;
      -			}
      -		}
      -
      -		//mouse over a node
      -		if(n)
      -		{
      -			//this.canvas.style.cursor = "move";
      -			if(!n.mouseOver)
      -			{
      -				//mouse enter
      -				n.mouseOver = true;
      -				this.node_over = n;
      -				this.dirty_canvas = true;
      -
      -				if(n.onMouseEnter) n.onMouseEnter(e);
      -			}
      -
      -			if(n.onMouseMove) n.onMouseMove(e);
      -
      -			//on top of input
      -			if(this.connecting_node)
      -			{
      -				var pos = this._highlight_input || [0,0]; //to store the output of isOverNodeInput
      -
      -				if( this.isOverNodeBox( n, e.canvasX, e.canvasY ) )
      -				{
      -					//mouse on top of the corner box, dont know what to do
      -				}
      -				else
      -				{
      -					var slot = this.isOverNodeInput( n, e.canvasX, e.canvasY, pos );
      -					if(slot != -1 && n.inputs[slot])
      -					{
      -						var slot_type = n.inputs[slot].type;
      -						if( LiteGraph.isValidConnection( this.connecting_output.type, slot_type ) )
      -							this._highlight_input = pos;
      -					}
      -					else
      -						this._highlight_input = null;
      -				}
      -			}
      -
      -			//Search for corner
      -			if( isInsideRectangle(e.canvasX, e.canvasY, n.pos[0] + n.size[0] - 5, n.pos[1] + n.size[1] - 5 ,5,5 ))
      -				this.canvas.style.cursor = "se-resize";
      -			else
      -				this.canvas.style.cursor = null;
      -		}
      -		else
      -			this.canvas.style.cursor = null;
      -
      -		if(this.node_capturing_input && this.node_capturing_input != n && this.node_capturing_input.onMouseMove)
      -		{
      -			this.node_capturing_input.onMouseMove(e);
      -		}
      -
      -
      -		if(this.node_dragged && !this.live_mode)
      -		{
      -			/*
      -			this.node_dragged.pos[0] += delta[0] / this.scale;
      -			this.node_dragged.pos[1] += delta[1] / this.scale;
      -			this.node_dragged.pos[0] = Math.round(this.node_dragged.pos[0]);
      -			this.node_dragged.pos[1] = Math.round(this.node_dragged.pos[1]);
      -			*/
      -
      -			for(var i in this.selected_nodes)
      -			{
      -				var n = this.selected_nodes[i];
      -
      -				n.pos[0] += delta[0] / this.scale;
      -				n.pos[1] += delta[1] / this.scale;
      -				//n.pos[0] = Math.round(n.pos[0]);
      -				//n.pos[1] = Math.round(n.pos[1]);
      -			}
      -
      -			this.dirty_canvas = true;
      -			this.dirty_bgcanvas = true;
      -		}
      -
      -		if(this.resizing_node && !this.live_mode)
      -		{
      -			this.resizing_node.size[0] += delta[0] / this.scale;
      -			this.resizing_node.size[1] += delta[1] / this.scale;
      -			var max_slots = Math.max( this.resizing_node.inputs ? this.resizing_node.inputs.length : 0, this.resizing_node.outputs ? this.resizing_node.outputs.length : 0);
      -			if(this.resizing_node.size[1] < max_slots * LiteGraph.NODE_SLOT_HEIGHT + 4)
      -				this.resizing_node.size[1] = max_slots * LiteGraph.NODE_SLOT_HEIGHT + 4;
      -			if(this.resizing_node.size[0] < LiteGraph.NODE_MIN_WIDTH)
      -				this.resizing_node.size[0] = LiteGraph.NODE_MIN_WIDTH;
      -
      -			this.canvas.style.cursor = "se-resize";
      -			this.dirty_canvas = true;
      -			this.dirty_bgcanvas = true;
      -		}
      -	}
      -
      -	/*
      -	if((this.dirty_canvas || this.dirty_bgcanvas) && this.rendering_timer_id == null)
      -		this.draw();
      -	*/
      -
      -	e.preventDefault();
      -	//e.stopPropagation();
      -	return false;
      -	//this is not really optimal
      -	//this.graph.change();
      -}
      -
      -LGraphCanvas.prototype.processMouseUp = function(e)
      -{
      -	if(!this.graph)
      -		return;
      -
      -	var window = this.getCanvasWindow();
      -	var document = window.document;
      -	LGraphCanvas.active_canvas = this;
      -
      -	//restore the mousemove event back to the canvas
      -	document.removeEventListener("mousemove", this._mousemove_callback, true );
      -	this.canvas.addEventListener("mousemove", this._mousemove_callback, true);
      -	document.removeEventListener("mouseup", this._mouseup_callback, true );
      -
      -	this.adjustMouseEvent(e);
      -
      -	if (e.which == 1) //left button
      -	{
      -		if( this.dragging_rectangle )
      -		{
      -			if(this.graph)
      -			{
      -				var nodes = this.graph._nodes;
      -				var node_bounding = new Float32Array(4);
      -				this.deselectAllNodes();
      -				if( this.dragging_rectangle[2] < 0 ) //flip if negative width
      -					this.dragging_rectangle[0] += this.dragging_rectangle[2];
      -				if( this.dragging_rectangle[3] < 0 ) //flip if negative height
      -					this.dragging_rectangle[1] += this.dragging_rectangle[3];
      -				this.dragging_rectangle[2] = Math.abs( this.dragging_rectangle[2] * this.scale ); //abs to convert negative width
      -				this.dragging_rectangle[3] = Math.abs( this.dragging_rectangle[3] * this.scale ); //abs to convert negative height
      -
      -				for(var i = 0; i < nodes.length; ++i)
      -				{
      -					var node = nodes[i];
      -					node.getBounding( node_bounding );
      -					if(!overlapBounding( this.dragging_rectangle, node_bounding ))
      -						continue; //out of the visible area
      -					this.selectNode( node, true );
      -				}
      -			}
      -			this.dragging_rectangle = null;
      -		}
      -		else if(this.connecting_node) //dragging a connection
      -		{
      -			this.dirty_canvas = true;
      -			this.dirty_bgcanvas = true;
      -
      -			var node = this.graph.getNodeOnPos( e.canvasX, e.canvasY, this.visible_nodes );
      -
      -			//node below mouse
      -			if(node)
      -			{
      -				if( this.connecting_output.type == LiteGraph.EVENT && this.isOverNodeBox( node, e.canvasX, e.canvasY ) )
      -				{
      -					this.connecting_node.connect( this.connecting_slot, node, LiteGraph.EVENT );
      -				}
      -				else
      -				{
      -					//slot below mouse? connect
      -					var slot = this.isOverNodeInput(node, e.canvasX, e.canvasY);
      -					if(slot != -1)
      -					{
      -						this.connecting_node.connect(this.connecting_slot, node, slot);
      -					}
      -					else
      -					{ //not on top of an input
      -						var input = node.getInputInfo(0);
      -						//auto connect
      -						if(this.connecting_output.type == LiteGraph.EVENT)
      -							this.connecting_node.connect( this.connecting_slot, node, LiteGraph.EVENT );
      -						else
      -							if(input && !input.link && LiteGraph.isValidConnection( input.type && this.connecting_output.type ) )
      -								this.connecting_node.connect( this.connecting_slot, node, 0 );
      -					}
      -				}
      -			}
      -
      -			this.connecting_output = null;
      -			this.connecting_pos = null;
      -			this.connecting_node = null;
      -			this.connecting_slot = -1;
      -
      -		}//not dragging connection
      -		else if(this.resizing_node)
      -		{
      -			this.dirty_canvas = true;
      -			this.dirty_bgcanvas = true;
      -			this.resizing_node = null;
      -		}
      -		else if(this.node_dragged) //node being dragged?
      -		{
      -			this.dirty_canvas = true;
      -			this.dirty_bgcanvas = true;
      -			this.node_dragged.pos[0] = Math.round(this.node_dragged.pos[0]);
      -			this.node_dragged.pos[1] = Math.round(this.node_dragged.pos[1]);
      -			if(this.graph.config.align_to_grid)
      -				this.node_dragged.alignToGrid();
      -			this.node_dragged = null;
      -		}
      -		else //no node being dragged
      -		{
      -			//get node over
      -			var node = this.graph.getNodeOnPos( e.canvasX, e.canvasY, this.visible_nodes );
      -
      -			var now = LiteGraph.getTime();
      -			if ( !node && (now - this.last_mouseclick) < 300 )
      -				this.deselectAllNodes();
      -
      -			this.dirty_canvas = true;
      -			this.dragging_canvas = false;
      -
      -			if( this.node_over && this.node_over.onMouseUp )
      -				this.node_over.onMouseUp(e, [e.canvasX - this.node_over.pos[0], e.canvasY - this.node_over.pos[1]] );
      -			if( this.node_capturing_input && this.node_capturing_input.onMouseUp )
      -				this.node_capturing_input.onMouseUp(e, [e.canvasX - this.node_capturing_input.pos[0], e.canvasY - this.node_capturing_input.pos[1]] );
      -		}
      -	}
      -	else if (e.which == 2) //middle button
      -	{
      -		//trace("middle");
      -		this.dirty_canvas = true;
      -		this.dragging_canvas = false;
      -	}
      -	else if (e.which == 3) //right button
      -	{
      -		//trace("right");
      -		this.dirty_canvas = true;
      -		this.dragging_canvas = false;
      -	}
      -
      -	/*
      -	if((this.dirty_canvas || this.dirty_bgcanvas) && this.rendering_timer_id == null)
      -		this.draw();
      -	*/
      -
      -	this.graph.change();
      -
      -	e.stopPropagation();
      -	e.preventDefault();
      -	return false;
      -}
      -
      -
      -LGraphCanvas.prototype.processMouseWheel = function(e)
      -{
      -	if(!this.graph || !this.allow_dragcanvas)
      -		return;
      -
      -	var delta = (e.wheelDeltaY != null ? e.wheelDeltaY : e.detail * -60);
      -
      -	this.adjustMouseEvent(e);
      -
      -	var zoom = this.scale;
      -
      -	if (delta > 0)
      -		zoom *= 1.1;
      -	else if (delta < 0)
      -		zoom *= 1/(1.1);
      -
      -	this.setZoom( zoom, [ e.localX, e.localY ] );
      -
      -	/*
      -	if(this.rendering_timer_id == null)
      -		this.draw();
      -	*/
      -
      -	this.graph.change();
      -
      -	e.preventDefault();
      -	return false; // prevent default
      -}
      -
      -LGraphCanvas.prototype.isOverNodeBox = function( node, canvasx, canvasy )
      -{
      -	var title_height = LiteGraph.NODE_TITLE_HEIGHT;
      -	if( isInsideRectangle( canvasx, canvasy, node.pos[0] + 2, node.pos[1] + 2 - title_height, title_height - 4,title_height - 4) )
      -		return true;
      -	return false;
      -}
      -
      -LGraphCanvas.prototype.isOverNodeInput = function(node, canvasx, canvasy, slot_pos )
      -{
      -	if(node.inputs)
      -		for(var i = 0, l = node.inputs.length; i < l; ++i)
      -		{
      -			var input = node.inputs[i];
      -			var link_pos = node.getConnectionPos(true,i);
      -			if( isInsideRectangle(canvasx, canvasy, link_pos[0] - 10, link_pos[1] - 5, 20,10) )
      -			{
      -				if(slot_pos)
      -				{
      -					slot_pos[0] = link_pos[0];
      -					slot_pos[1] = link_pos[1];
      -				}
      -				return i;
      -			}
      -		}
      -	return -1;
      -}
      -
      -LGraphCanvas.prototype.processKey = function(e)
      -{
      -	if(!this.graph)
      -		return;
      -
      -	var block_default = false;
      -	//console.log(e); //debug
      -
      -	if(e.target.localName == "input")
      -		return;
      -
      -	if(e.type == "keydown")
      -	{
      -		if(e.keyCode == 32)
      -		{
      -			this.dragging_canvas = true;
      -			block_default = true;
      -		}
      -
      -		//select all Control A
      -		if(e.keyCode == 65 && e.ctrlKey)
      -		{
      -			this.selectNodes();
      -			block_default = true;
      -		}
      -
      -		if(e.code == "KeyC" && (e.metaKey || e.ctrlKey) && !e.shiftKey ) //copy
      -		{
      -			if(this.selected_nodes)
      -			{
      -				this.copyToClipboard();
      -				block_default = true;
      -			}
      -		}
      -
      -		if(e.code == "KeyV" && (e.metaKey || e.ctrlKey) && !e.shiftKey ) //paste
      -		{
      -			this.pasteFromClipboard();
      -		}
      -
      -		//delete or backspace
      -		if(e.keyCode == 46 || e.keyCode == 8)
      -		{
      -			this.deleteSelectedNodes();
      -			block_default = true;
      -		}
      -
      -		//collapse
      -		//...
      -
      -		//TODO
      -		if(this.selected_nodes)
      -			for (var i in this.selected_nodes)
      -				if(this.selected_nodes[i].onKeyDown)
      -					this.selected_nodes[i].onKeyDown(e);
      -	}
      -	else if( e.type == "keyup" )
      -	{
      -		if(e.keyCode == 32)
      -			this.dragging_canvas = false;
      -
      -		if(this.selected_nodes)
      -			for (var i in this.selected_nodes)
      -				if(this.selected_nodes[i].onKeyUp)
      -					this.selected_nodes[i].onKeyUp(e);
      -	}
      -
      -	this.graph.change();
      -
      -	if(block_default)
      -	{
      -		e.preventDefault();
      -		return false;
      -	}
      -}
      -
      -LGraphCanvas.prototype.copyToClipboard = function()
      -{
      -	var clipboard_info = {
      -		nodes: [],
      -		links: []
      -	};
      -	var index = 0;
      -	var selected_nodes_array = [];
      -	for(var i in this.selected_nodes)
      -	{
      -		var node = this.selected_nodes[i];
      -		node._relative_id = index;
      -		selected_nodes_array.push( node );
      -		index += 1;
      -	}
      -
      -	for(var i = 0; i < selected_nodes_array.length; ++i)
      -	{
      -		var node = selected_nodes_array[i];
      -		clipboard_info.nodes.push( node.clone().serialize() );
      -		if(node.inputs && node.inputs.length)
      -			for(var j = 0; j < node.inputs.length; ++j)
      -			{
      -				var input = node.inputs[j];
      -				if(!input || input.link == null)
      -					continue;
      -				var link_info = this.graph.links[ input.link ];
      -				if(!link_info)
      -					continue;
      -				var target_node = this.graph.getNodeById( link_info.origin_id );
      -				if(!target_node || !this.selected_nodes[ target_node.id ] ) //improve this by allowing connections to non-selected nodes
      -					continue; //not selected
      -				clipboard_info.links.push([ target_node._relative_id, j, node._relative_id, link_info.target_slot ]);
      -			}
      -	}
      -	localStorage.setItem( "litegrapheditor_clipboard", JSON.stringify( clipboard_info ) );
      -}
      -
      -LGraphCanvas.prototype.pasteFromClipboard = function()
      -{
      -	var data = localStorage.getItem( "litegrapheditor_clipboard" );
      -	if(!data)
      -		return;
      -
      -	//create nodes
      -	var clipboard_info = JSON.parse(data);
      -	var nodes = [];
      -	for(var i = 0; i < clipboard_info.nodes.length; ++i)
      -	{
      -		var node_data = clipboard_info.nodes[i];
      -		var node = LiteGraph.createNode( node_data.type );
      -		if(node)
      -		{
      -			node.configure(node_data);
      -			node.pos[0] += 5;
      -			node.pos[1] += 5;
      -			this.graph.add( node );
      -			nodes.push( node );
      -		}
      -	}
      -
      -	//create links
      -	for(var i = 0; i < clipboard_info.links.length; ++i)
      -	{
      -		var link_info = clipboard_info.links[i];
      -		var origin_node = nodes[ link_info[0] ];
      -		var target_node = nodes[ link_info[2] ];
      -		origin_node.connect( link_info[1], target_node, link_info[3] );
      -	}
      -
      -	this.selectNodes( nodes );
      -}
      -
      -LGraphCanvas.prototype.processDrop = function(e)
      -{
      -	e.preventDefault();
      -	this.adjustMouseEvent(e);
      -
      -
      -	var pos = [e.canvasX,e.canvasY];
      -	var node = this.graph.getNodeOnPos(pos[0],pos[1]);
      -
      -	if(!node)
      -	{
      -		var r = null;
      -		if(this.onDropItem)
      -			r = this.onDropItem( event );
      -		if(!r)
      -			this.checkDropItem(e);
      -		return;
      -	}
      -
      -	if( node.onDropFile || node.onDropData )
      -	{
      -		var files = e.dataTransfer.files;
      -		if(files && files.length)
      -		{
      -			for(var i=0; i < files.length; i++)
      -			{
      -				var file = e.dataTransfer.files[0];
      -				var filename = file.name;
      -				var ext = LGraphCanvas.getFileExtension( filename );
      -				//console.log(file);
      -
      -				if(node.onDropFile)
      -					node.onDropFile(file);
      -
      -				if(node.onDropData)
      -				{
      -					//prepare reader
      -					var reader = new FileReader();
      -					reader.onload = function (event) {
      -						//console.log(event.target);
      -						var data = event.target.result;
      -						node.onDropData( data, filename, file );
      -					};
      -
      -					//read data
      -					var type = file.type.split("/")[0];
      -					if(type == "text" || type == "")
      -						reader.readAsText(file);
      -					else if (type == "image")
      -						reader.readAsDataURL(file);
      -					else
      -						reader.readAsArrayBuffer(file);
      -				}
      -			}
      -		}
      -	}
      -
      -	if(node.onDropItem)
      -	{
      -		if( node.onDropItem( event ) )
      -			return true;
      -	}
      -
      -	if(this.onDropItem)
      -		return this.onDropItem( event );
      -
      -	return false;
      -}
      -
      -//called if the graph doesnt have a default drop item behaviour
      -LGraphCanvas.prototype.checkDropItem = function(e)
      -{
      -	if(e.dataTransfer.files.length)
      -	{
      -		var file = e.dataTransfer.files[0];
      -		var ext = LGraphCanvas.getFileExtension( file.name ).toLowerCase();
      -		var nodetype = LiteGraph.node_types_by_file_extension[ext];
      -		if(nodetype)
      -		{
      -			var node = LiteGraph.createNode( nodetype.type );
      -			node.pos = [e.canvasX, e.canvasY];
      -			this.graph.add( node );
      -			if( node.onDropFile )
      -				node.onDropFile( file );
      -		}
      -	}
      -}
      -
      -
      -LGraphCanvas.prototype.processNodeDblClicked = function(n)
      -{
      -	if(this.onShowNodePanel)
      -		this.onShowNodePanel(n);
      -
      -	if(this.onNodeDblClicked)
      -		this.onNodeDblClicked(n);
      -
      -	this.setDirty(true);
      -}
      -
      -LGraphCanvas.prototype.processNodeSelected = function(node,e)
      -{
      -	this.selectNode( node, e && e.shiftKey );
      -	if(this.onNodeSelected)
      -		this.onNodeSelected(node);
      -}
      -
      -LGraphCanvas.prototype.processNodeDeselected = function(node)
      -{
      -	this.deselectNode(node);
      -	if(this.onNodeDeselected)
      -		this.onNodeDeselected(node);
      -}
      -
      -LGraphCanvas.prototype.selectNode = function( node, add_to_current_selection )
      -{
      -	if(node == null)
      -		this.deselectAllNodes();
      -	else
      -		this.selectNodes([node], add_to_current_selection );
      -}
      -
      -LGraphCanvas.prototype.selectNodes = function( nodes, add_to_current_selection )
      -{
      -	if(!add_to_current_selection)
      -		this.deselectAllNodes();
      -
      -	nodes = nodes || this.graph._nodes;
      -	for(var i = 0; i < nodes.length; ++i)
      -	{
      -		var node = nodes[i];
      -		if(node.selected)
      -			continue;
      -
      -		if( !node.selected && node.onSelected )
      -			node.onSelected();
      -		node.selected = true;
      -		this.selected_nodes[ node.id ] = node;
      -
      -		if(node.inputs)
      -			for(var i = 0; i < node.inputs.length; ++i)
      -				this.highlighted_links[ node.inputs[i].link ] = true;
      -		if(node.outputs)
      -			for(var i = 0; i < node.outputs.length; ++i)
      -			{
      -				var out = node.outputs[i];
      -				if( out.links )
      -					for(var j = 0; j < out.links.length; ++j)
      -						this.highlighted_links[ out.links[j] ] = true;
      -			}
      -
      -	}
      -
      -	this.setDirty(true);
      -}
      -
      -LGraphCanvas.prototype.deselectNode = function( node )
      -{
      -	if(!node.selected)
      -		return;
      -	if(node.onDeselected)
      -		node.onDeselected();
      -	node.selected = false;
      -
      -	//remove highlighted
      -	if(node.inputs)
      -		for(var i = 0; i < node.inputs.length; ++i)
      -			delete this.highlighted_links[ node.inputs[i].link ];
      -	if(node.outputs)
      -		for(var i = 0; i < node.outputs.length; ++i)
      -		{
      -			var out = node.outputs[i];
      -			if( out.links )
      -				for(var j = 0; j < out.links.length; ++j)
      -					delete this.highlighted_links[ out.links[j] ];
      -		}
      -}
      -
      -LGraphCanvas.prototype.deselectAllNodes = function()
      -{
      -	if(!this.graph)
      -		return;
      -	var nodes = this.graph._nodes;
      -	for(var i = 0, l = nodes.length; i < l; ++i)
      -	{
      -		var node = nodes[i];
      -		if(!node.selected)
      -			continue;
      -		if(node.onDeselected)
      -			node.onDeselected();
      -		node.selected = false;
      -	}
      -	this.selected_nodes = {};
      -	this.highlighted_links = {};
      -	this.setDirty(true);
      -}
      -
      -LGraphCanvas.prototype.deleteSelectedNodes = function()
      -{
      -	for(var i in this.selected_nodes)
      -	{
      -		var m = this.selected_nodes[i];
      -		//if(m == this.node_in_panel) this.showNodePanel(null);
      -		this.graph.remove(m);
      -	}
      -	this.selected_nodes = {};
      -	this.highlighted_links = {};
      -	this.setDirty(true);
      -}
      -
      -LGraphCanvas.prototype.centerOnNode = function(node)
      -{
      -	this.offset[0] = -node.pos[0] - node.size[0] * 0.5 + (this.canvas.width * 0.5 / this.scale);
      -	this.offset[1] = -node.pos[1] - node.size[1] * 0.5 + (this.canvas.height * 0.5 / this.scale);
      -	this.setDirty(true,true);
      -}
      -
      -LGraphCanvas.prototype.adjustMouseEvent = function(e)
      -{
      -	var b = this.canvas.getBoundingClientRect();
      -	e.localX = e.pageX - b.left;
      -	e.localY = e.pageY - b.top;
      -
      -	e.canvasX = e.localX / this.scale - this.offset[0];
      -	e.canvasY = e.localY / this.scale - this.offset[1];
      -}
      -
      -LGraphCanvas.prototype.setZoom = function(value, zooming_center)
      -{
      -	if(!zooming_center)
      -		zooming_center = [this.canvas.width * 0.5,this.canvas.height * 0.5];
      -
      -	var center = this.convertOffsetToCanvas( zooming_center );
      -
      -	this.scale = value;
      -
      -	if(this.scale > this.max_zoom)
      -		this.scale = this.max_zoom;
      -	else if(this.scale < this.min_zoom)
      -		this.scale = this.min_zoom;
      -
      -	var new_center = this.convertOffsetToCanvas( zooming_center );
      -	var delta_offset = [new_center[0] - center[0], new_center[1] - center[1]];
      -
      -	this.offset[0] += delta_offset[0];
      -	this.offset[1] += delta_offset[1];
      -
      -	this.dirty_canvas = true;
      -	this.dirty_bgcanvas = true;
      -}
      -
      -LGraphCanvas.prototype.convertOffsetToCanvas = function( pos, out )
      -{
      -	out = out || [];
      -	out[0] = pos[0] / this.scale - this.offset[0];
      -	out[1] = pos[1] / this.scale - this.offset[1];
      -	return out;
      -}
      -
      -LGraphCanvas.prototype.convertCanvasToOffset = function( pos, out )
      -{
      -	out = out || [];
      -	out[0] = (pos[0] + this.offset[0]) * this.scale;
      -	out[1] = (pos[1] + this.offset[1]) * this.scale;
      -	return out;
      -}
      -
      -LGraphCanvas.prototype.convertEventToCanvas = function(e)
      -{
      -	var rect = this.canvas.getBoundingClientRect();
      -	return this.convertOffsetToCanvas([e.pageX - rect.left,e.pageY - rect.top]);
      -}
      -
      -LGraphCanvas.prototype.bringToFront = function(n)
      -{
      -	var i = this.graph._nodes.indexOf(n);
      -	if(i == -1) return;
      -
      -	this.graph._nodes.splice(i,1);
      -	this.graph._nodes.push(n);
      -}
      -
      -LGraphCanvas.prototype.sendToBack = function(n)
      -{
      -	var i = this.graph._nodes.indexOf(n);
      -	if(i == -1) return;
      -
      -	this.graph._nodes.splice(i,1);
      -	this.graph._nodes.unshift(n);
      -}
      -
      -/* Interaction */
      -
      -
      -
      -/* LGraphCanvas render */
      -var temp = new Float32Array(4);
      -
      -LGraphCanvas.prototype.computeVisibleNodes = function( nodes, out )
      -{
      -	var visible_nodes = out || [];
      -	visible_nodes.length = 0;
      -	nodes = nodes || this.graph._nodes;
      -	for(var i = 0, l = nodes.length; i < l; ++i)
      -	{
      -		var n = nodes[i];
      -
      -		//skip rendering nodes in live mode
      -		if(this.live_mode && !n.onDrawBackground && !n.onDrawForeground)
      -			continue;
      -
      -		if(!overlapBounding( this.visible_area, n.getBounding( temp ) ))
      -			continue; //out of the visible area
      -
      -		visible_nodes.push(n);
      -	}
      -	return visible_nodes;
      -}
      -
      -LGraphCanvas.prototype.draw = function(force_canvas, force_bgcanvas)
      -{
      -	if(!this.canvas)
      -		return;
      -
      -	//fps counting
      -	var now = LiteGraph.getTime();
      -	this.render_time = (now - this.last_draw_time)*0.001;
      -	this.last_draw_time = now;
      -
      -	if(this.graph)
      -	{
      -		var start = [-this.offset[0], -this.offset[1] ];
      -		var end = [start[0] + this.canvas.width / this.scale, start[1] + this.canvas.height / this.scale];
      -		this.visible_area = new Float32Array([ start[0], start[1], end[0] - start[0], end[1] - start[1] ]);
      -	}
      -
      -	if(this.dirty_bgcanvas || force_bgcanvas || this.always_render_background || (this.graph && this.graph._last_trigger_time && (now - this.graph._last_trigger_time) < 1000) )
      -		this.drawBackCanvas();
      -
      -	if(this.dirty_canvas || force_canvas)
      -		this.drawFrontCanvas();
      -
      -	this.fps = this.render_time ? (1.0 / this.render_time) : 0;
      -	this.frame += 1;
      -}
      -
      -LGraphCanvas.prototype.drawFrontCanvas = function()
      -{
      -	if(!this.ctx)
      -		this.ctx = this.bgcanvas.getContext("2d");
      -	var ctx = this.ctx;
      -	if(!ctx) //maybe is using webgl...
      -		return;
      -
      -	if(ctx.start2D)
      -		ctx.start2D();
      -
      -	var canvas = this.canvas;
      -
      -	//reset in case of error
      -	ctx.restore();
      -	ctx.setTransform(1, 0, 0, 1, 0, 0);
      -
      -	//clip dirty area if there is one, otherwise work in full canvas
      -	if(this.dirty_area)
      -	{
      -		ctx.save();
      -		ctx.beginPath();
      -		ctx.rect(this.dirty_area[0],this.dirty_area[1],this.dirty_area[2],this.dirty_area[3]);
      -		ctx.clip();
      -	}
      -
      -	//clear
      -	//canvas.width = canvas.width;
      -	if(this.clear_background)
      -		ctx.clearRect(0,0,canvas.width, canvas.height);
      -
      -	//draw bg canvas
      -	if(this.bgcanvas == this.canvas)
      -		this.drawBackCanvas();
      -	else
      -		ctx.drawImage(this.bgcanvas,0,0);
      -
      -	//rendering
      -	if(this.onRender)
      -		this.onRender(canvas, ctx);
      -
      -	//info widget
      -	if(this.show_info)
      -		this.renderInfo(ctx);
      -
      -	if(this.graph)
      -	{
      -		//apply transformations
      -		ctx.save();
      -		ctx.scale(this.scale,this.scale);
      -		ctx.translate(this.offset[0],this.offset[1]);
      -
      -		//draw nodes
      -		var drawn_nodes = 0;
      -		var visible_nodes = this.computeVisibleNodes( null, this.visible_nodes );
      -
      -		for (var i = 0; i < visible_nodes.length; ++i)
      -		{
      -			var node = visible_nodes[i];
      -
      -			//transform coords system
      -			ctx.save();
      -			ctx.translate( node.pos[0], node.pos[1] );
      -
      -			//Draw
      -			this.drawNode(node, ctx );
      -			drawn_nodes += 1;
      -
      -			//Restore
      -			ctx.restore();
      -		}
      -
      -		//connections ontop?
      -		if(this.graph.config.links_ontop)
      -			if(!this.live_mode)
      -				this.drawConnections(ctx);
      -
      -		//current connection
      -		if(this.connecting_pos != null)
      -		{
      -			ctx.lineWidth = this.connections_width;
      -			var link_color = null;
      -			switch( this.connecting_output.type )
      -			{
      -				case LiteGraph.EVENT: link_color = "#F85"; break;
      -				default:
      -					link_color = "#AFA";
      -			}
      -			//the connection being dragged by the mouse
      -			this.renderLink( ctx, this.connecting_pos, [this.canvas_mouse[0],this.canvas_mouse[1]], null, false, null, link_color );
      -
      -			ctx.beginPath();
      -				if( this.connecting_output.type === LiteGraph.EVENT )
      -					ctx.rect( (this.connecting_pos[0] - 6) + 0.5, (this.connecting_pos[1] - 5) + 0.5,14,10);
      -				else
      -					ctx.arc( this.connecting_pos[0], this.connecting_pos[1],4,0,Math.PI*2);
      -			ctx.fill();
      -
      -			ctx.fillStyle = "#ffcc00";
      -			if(this._highlight_input)
      -			{
      -				ctx.beginPath();
      -					ctx.arc( this._highlight_input[0], this._highlight_input[1],6,0,Math.PI*2);
      -				ctx.fill();
      -			}
      -		}
      -
      -		if( this.dragging_rectangle )
      -		{
      -			ctx.strokeStyle = "#FFF";
      -			ctx.strokeRect( this.dragging_rectangle[0], this.dragging_rectangle[1], this.dragging_rectangle[2], this.dragging_rectangle[3] );
      -		}
      -
      -
      -		ctx.restore();
      -	}
      -
      -	if(this.dirty_area)
      -	{
      -		ctx.restore();
      -		//this.dirty_area = null;
      -	}
      -
      -	if(ctx.finish2D) //this is a function I use in webgl renderer
      -		ctx.finish2D();
      -
      -	this.dirty_canvas = false;
      -}
      -
      -LGraphCanvas.prototype.renderInfo = function( ctx, x, y )
      -{
      -	x = x || 0;
      -	y = y || 0;
      -
      -	ctx.save();
      -	ctx.translate( x, y );
      -
      -	ctx.font = "10px Arial";
      -	ctx.fillStyle = "#888";
      -	if(this.graph)
      -	{
      -		ctx.fillText( "T: " + this.graph.globaltime.toFixed(2)+"s",5,13*1 );
      -		ctx.fillText( "I: " + this.graph.iteration,5,13*2 );
      -		ctx.fillText( "F: " + this.frame,5,13*3 );
      -		ctx.fillText( "FPS:" + this.fps.toFixed(2),5,13*4 );
      -	}
      -	else
      -		ctx.fillText( "No graph selected",5,13*1 );
      -	ctx.restore();
      -}
      -
      -LGraphCanvas.prototype.drawBackCanvas = function()
      -{
      -	var canvas = this.bgcanvas;
      -	if(canvas.width != this.canvas.width ||
      -		canvas.height != this.canvas.height)
      -	{
      -		canvas.width = this.canvas.width;
      -		canvas.height = this.canvas.height;
      -	}
      -
      -	if(!this.bgctx)
      -		this.bgctx = this.bgcanvas.getContext("2d");
      -	var ctx = this.bgctx;
      -	if(ctx.start)
      -		ctx.start();
      -
      -	//clear
      -	if(this.clear_background)
      -		ctx.clearRect(0,0,canvas.width, canvas.height);
      -
      -	if(this._graph_stack && this._graph_stack.length)
      -	{
      -		ctx.strokeStyle = this._graph_stack[ this._graph_stack.length - 1].bgcolor;
      -		ctx.lineWidth = 10;
      -		ctx.strokeRect(1,1,canvas.width-2,canvas.height-2);
      -		ctx.lineWidth = 1;
      -	}
      -
      -	//reset in case of error
      -	ctx.restore();
      -	ctx.setTransform(1, 0, 0, 1, 0, 0);
      -
      -	if(this.graph)
      -	{
      -		//apply transformations
      -		ctx.save();
      -		ctx.scale(this.scale,this.scale);
      -		ctx.translate(this.offset[0],this.offset[1]);
      -
      -		//render BG
      -		if(this.background_image && this.scale > 0.5)
      -		{
      -			ctx.globalAlpha = (1.0 - 0.5 / this.scale) * this.editor_alpha;
      -			ctx.imageSmoothingEnabled = ctx.mozImageSmoothingEnabled = ctx.imageSmoothingEnabled = false;
      -			if(!this._bg_img || this._bg_img.name != this.background_image)
      -			{
      -				this._bg_img = new Image();
      -				this._bg_img.name = this.background_image;
      -				this._bg_img.src = this.background_image;
      -				var that = this;
      -				this._bg_img.onload = function() {
      -					that.draw(true,true);
      -				}
      -			}
      -
      -			var pattern = null;
      -			if(this._pattern == null && this._bg_img.width > 0)
      -			{
      -				pattern = ctx.createPattern( this._bg_img, 'repeat' );
      -				this._pattern_img = this._bg_img;
      -				this._pattern = pattern;
      -			}
      -			else
      -				pattern = this._pattern;
      -			if(pattern)
      -			{
      -				ctx.fillStyle = pattern;
      -				ctx.fillRect(this.visible_area[0],this.visible_area[1],this.visible_area[2],this.visible_area[3]);
      -				ctx.fillStyle = "transparent";
      -			}
      -
      -			ctx.globalAlpha = 1.0;
      -			ctx.imageSmoothingEnabled = ctx.mozImageSmoothingEnabled = ctx.imageSmoothingEnabled = true;
      -		}
      -
      -		if(this.onBackgroundRender)
      -			this.onBackgroundRender(canvas, ctx);
      -
      -		//DEBUG: show clipping area
      -		//ctx.fillStyle = "red";
      -		//ctx.fillRect( this.visible_area[0] + 10, this.visible_area[1] + 10, this.visible_area[2] - 20, this.visible_area[3] - 20);
      -
      -		//bg
      -		if (this.render_canvas_area) {
      -			ctx.strokeStyle = "#235";
      -			ctx.strokeRect(0,0,canvas.width,canvas.height);
      -		}
      -
      -		if(this.render_connections_shadows)
      -		{
      -			ctx.shadowColor = "#000";
      -			ctx.shadowOffsetX = 0;
      -			ctx.shadowOffsetY = 0;
      -			ctx.shadowBlur = 6;
      -		}
      -		else
      -			ctx.shadowColor = "rgba(0,0,0,0)";
      -
      -		//draw connections
      -		if(!this.live_mode)
      -			this.drawConnections(ctx);
      -
      -		ctx.shadowColor = "rgba(0,0,0,0)";
      -
      -		//restore state
      -		ctx.restore();
      -	}
      -
      -	if(ctx.finish)
      -		ctx.finish();
      -
      -	this.dirty_bgcanvas = false;
      -	this.dirty_canvas = true; //to force to repaint the front canvas with the bgcanvas
      -}
      -
      -var temp_vec2 = new Float32Array(2);
      -
      -/* Renders the LGraphNode on the canvas */
      -LGraphCanvas.prototype.drawNode = function(node, ctx )
      -{
      -	var glow = false;
      -
      -	var color = node.color || LiteGraph.NODE_DEFAULT_COLOR;
      -	//if (this.selected) color = "#88F";
      -
      -	var render_title = true;
      -	if(node.flags.skip_title_render || node.graph.isLive())
      -		render_title = false;
      -	if(node.mouseOver)
      -		render_title = true;
      -
      -	//shadow and glow
      -	if (node.mouseOver) glow = true;
      -
      -	if(node.selected)
      -	{
      -		/*
      -		ctx.shadowColor = "#EEEEFF";//glow ? "#AAF" : "#000";
      -		ctx.shadowOffsetX = 0;
      -		ctx.shadowOffsetY = 0;
      -		ctx.shadowBlur = 1;
      -		*/
      -	}
      -	else if(this.render_shadows)
      -	{
      -		ctx.shadowColor = "rgba(0,0,0,0.5)";
      -		ctx.shadowOffsetX = 2;
      -		ctx.shadowOffsetY = 2;
      -		ctx.shadowBlur = 3;
      -	}
      -	else
      -		ctx.shadowColor = "transparent";
      -
      -	//only render if it forces it to do it
      -	if(this.live_mode)
      -	{
      -		if(!node.flags.collapsed)
      -		{
      -			ctx.shadowColor = "transparent";
      -			//if(node.onDrawBackground)
      -			//	node.onDrawBackground(ctx);
      -			if(node.onDrawForeground)
      -				node.onDrawForeground(ctx);
      -		}
      -
      -		return;
      -	}
      -
      -	//draw in collapsed form
      -	/*
      -	if(node.flags.collapsed)
      -	{
      -		if(!node.onDrawCollapsed || node.onDrawCollapsed(ctx) == false)
      -			this.drawNodeCollapsed(node, ctx, color, node.bgcolor);
      -		return;
      -	}
      -	*/
      -
      -	var editor_alpha = this.editor_alpha;
      -	ctx.globalAlpha = editor_alpha;
      -
      -	//clip if required (mask)
      -	var shape = node._shape || LiteGraph.BOX_SHAPE;
      -	var size = temp_vec2;
      -	temp_vec2.set( node.size );
      -	if(node.flags.collapsed)
      -	{
      -		size[0] = LiteGraph.NODE_COLLAPSED_WIDTH;
      -		size[1] = 0;
      -	}
      -
      -	//Start clipping
      -	if(node.flags.clip_area)
      -	{
      -		ctx.save();
      -		if(shape == LiteGraph.BOX_SHAPE)
      -		{
      -			ctx.beginPath();
      -			ctx.rect(0,0,size[0], size[1]);
      -		}
      -		else if (shape == LiteGraph.ROUND_SHAPE)
      -		{
      -			ctx.roundRect(0,0,size[0], size[1],10);
      -		}
      -		else if (shape == LiteGraph.CIRCLE_SHAPE)
      -		{
      -			ctx.beginPath();
      -			ctx.arc(size[0] * 0.5, size[1] * 0.5, size[0] * 0.5, 0, Math.PI*2);
      -		}
      -		ctx.clip();
      -	}
      -
      -	//draw shape
      -	this.drawNodeShape(node, ctx, size, color, node.bgcolor, !render_title, node.selected );
      -	ctx.shadowColor = "transparent";
      -
      -	//connection slots
      -	ctx.textAlign = "left";
      -	ctx.font = this.inner_text_font;
      -
      -	var render_text = this.scale > 0.6;
      -
      -	var out_slot = this.connecting_output;
      -
      -	//render inputs and outputs
      -	if(!node.flags.collapsed)
      -	{
      -		//input connection slots
      -		if(node.inputs)
      -			for(var i = 0; i < node.inputs.length; i++)
      -			{
      -				var slot = node.inputs[i];
      -
      -				ctx.globalAlpha = editor_alpha;
      -				//change opacity of incompatible slots
      -				if ( this.connecting_node && LiteGraph.isValidConnection( slot.type && out_slot.type ) )
      -					ctx.globalAlpha = 0.4 * editor_alpha;
      -
      -				ctx.fillStyle = slot.link != null ? this.default_connection_color.input_on : this.default_connection_color.input_off;
      -
      -				var pos = node.getConnectionPos(true,i);
      -				pos[0] -= node.pos[0];
      -				pos[1] -= node.pos[1];
      -
      -				ctx.beginPath();
      -
      -				if (slot.type === LiteGraph.EVENT)
      -					ctx.rect((pos[0] - 6) + 0.5, (pos[1] - 5) + 0.5,14,10);
      -				else
      -					ctx.arc(pos[0],pos[1],4,0,Math.PI*2);
      -
      -				ctx.fill();
      -
      -				//render name
      -				if(render_text)
      -				{
      -					var text = slot.label != null ? slot.label : slot.name;
      -					if(text)
      -					{
      -						ctx.fillStyle = color;
      -						ctx.fillText(text,pos[0] + 10,pos[1] + 5);
      -					}
      -				}
      -			}
      -
      -		//output connection slots
      -		if(this.connecting_node)
      -			ctx.globalAlpha = 0.4 * editor_alpha;
      -
      -		ctx.lineWidth = 1;
      -
      -		ctx.textAlign = "right";
      -		ctx.strokeStyle = "black";
      -		if(node.outputs)
      -			for(var i = 0; i < node.outputs.length; i++)
      -			{
      -				var slot = node.outputs[i];
      -
      -				var pos = node.getConnectionPos(false,i);
      -				pos[0] -= node.pos[0];
      -				pos[1] -= node.pos[1];
      -
      -				ctx.fillStyle = slot.links && slot.links.length ? this.default_connection_color.output_on : this.default_connection_color.output_off;
      -				ctx.beginPath();
      -				//ctx.rect( node.size[0] - 14,i*14,10,10);
      -
      -				if (slot.type === LiteGraph.EVENT)
      -					ctx.rect((pos[0] - 6) + 0.5,(pos[1] - 5) + 0.5,14,10);
      -				else
      -					ctx.arc( pos[0],pos[1],4,0, Math.PI*2 );
      -
      -				//trigger
      -				//if(slot.node_id != null && slot.slot == -1)
      -				//	ctx.fillStyle = "#F85";
      -
      -				//if(slot.links != null && slot.links.length)
      -				ctx.fill();
      -				ctx.stroke();
      -
      -				//render output name
      -				if(render_text)
      -				{
      -					var text = slot.label != null ? slot.label : slot.name;
      -					if(text)
      -					{
      -						ctx.fillStyle = color;
      -						ctx.fillText(text, pos[0] - 10,pos[1] + 5);
      -					}
      -				}
      -			}
      -
      -		ctx.textAlign = "left";
      -		ctx.globalAlpha = 1;
      -
      -		if(node.onDrawForeground)
      -			node.onDrawForeground(ctx);
      -	}//!collapsed
      -
      -	if(node.flags.clip_area)
      -		ctx.restore();
      -
      -	ctx.globalAlpha = 1.0;
      -}
      -
      -/* Renders the node shape */
      -LGraphCanvas.prototype.drawNodeShape = function(node, ctx, size, fgcolor, bgcolor, no_title, selected )
      -{
      -	//bg rect
      -	ctx.strokeStyle = fgcolor || LiteGraph.NODE_DEFAULT_COLOR;
      -	ctx.fillStyle = bgcolor || LiteGraph.NODE_DEFAULT_BGCOLOR;
      -
      -	/* gradient test
      -	var grad = ctx.createLinearGradient(0,0,0,node.size[1]);
      -	grad.addColorStop(0, "#AAA");
      -	grad.addColorStop(0.5, fgcolor || LiteGraph.NODE_DEFAULT_COLOR);
      -	grad.addColorStop(1, bgcolor || LiteGraph.NODE_DEFAULT_BGCOLOR);
      -	ctx.fillStyle = grad;
      -	//*/
      -
      -	var title_height = LiteGraph.NODE_TITLE_HEIGHT;
      -
      -	//render depending on shape
      -	var shape = node._shape || LiteGraph.BOX_SHAPE;
      -	if(shape == LiteGraph.BOX_SHAPE)
      -	{
      -		ctx.beginPath();
      -		ctx.rect(0,no_title ? 0 : -title_height, size[0]+1, no_title ? size[1] : size[1] + title_height);
      -		ctx.fill();
      -		ctx.shadowColor = "transparent";
      -
      -		if(selected)
      -		{
      -			ctx.strokeStyle = "#CCC";
      -			ctx.strokeRect(-0.5,no_title ? -0.5 : -title_height + -0.5, size[0]+2, no_title ? (size[1]+2) : (size[1] + title_height+2) - 1);
      -			ctx.strokeStyle = fgcolor;
      -		}
      -	}
      -	else if (shape == LiteGraph.ROUND_SHAPE)
      -	{
      -		ctx.roundRect(0,no_title ? 0 : -title_height,size[0], no_title ? size[1] : size[1] + title_height, 10);
      -		ctx.fill();
      -	}
      -	else if (shape == LiteGraph.CIRCLE_SHAPE)
      -	{
      -		ctx.beginPath();
      -		ctx.arc(size[0] * 0.5, size[1] * 0.5, size[0] * 0.5, 0, Math.PI*2);
      -		ctx.fill();
      -	}
      -
      -	ctx.shadowColor = "transparent";
      -
      -	//ctx.stroke();
      -
      -	//image
      -	if (node.bgImage && node.bgImage.width)
      -		ctx.drawImage( node.bgImage, (size[0] - node.bgImage.width) * 0.5 , (size[1] - node.bgImage.height) * 0.5);
      -
      -	if(node.bgImageUrl && !node.bgImage)
      -		node.bgImage = node.loadImage(node.bgImageUrl);
      -
      -	if(node.onDrawBackground)
      -		node.onDrawBackground(ctx);
      -
      -	//title bg (remember, it is rendered ABOVE the node
      -	if(!no_title)
      -	{
      -		ctx.fillStyle = fgcolor || LiteGraph.NODE_DEFAULT_COLOR;
      -		var old_alpha = ctx.globalAlpha;
      -		ctx.globalAlpha = 0.5 * old_alpha;
      -		if(shape == LiteGraph.BOX_SHAPE)
      -		{
      -			ctx.beginPath();
      -			ctx.rect(0, -title_height, size[0]+1, title_height);
      -			ctx.fill()
      -			//ctx.stroke();
      -		}
      -		else if (shape == LiteGraph.ROUND_SHAPE)
      -		{
      -			ctx.roundRect(0,-title_height,size[0], title_height,10,0);
      -			//ctx.fillRect(0,8,size[0],NODE_TITLE_HEIGHT - 12);
      -			ctx.fill();
      -			//ctx.stroke();
      -		}
      -		/*
      -		else if (shape == LiteGraph.CIRCLE_SHAPE)
      -		{
      -			ctx.beginPath();
      -			ctx.arc(title_height *0.5, title_height * -0.5, (title_height - 6) *0.5,0,Math.PI*2);
      -			ctx.fill();
      -		}
      -		*/
      -
      -		//title box
      -		ctx.fillStyle = node.boxcolor || LiteGraph.NODE_DEFAULT_BOXCOLOR;
      -		ctx.beginPath();
      -		if (shape == LiteGraph.ROUND_SHAPE || shape == LiteGraph.CIRCLE_SHAPE)
      -			ctx.arc(title_height *0.5, title_height * -0.5, (title_height - 6) *0.5,0,Math.PI*2);
      -		else
      -			ctx.rect(3,-title_height + 3,title_height - 6,title_height - 6);
      -		ctx.fill();
      -		ctx.globalAlpha = old_alpha;
      -
      -		//title text
      -		ctx.font = this.title_text_font;
      -		var title = node.getTitle();
      -		if(title && this.scale > 0.5)
      -		{
      -			ctx.fillStyle = LiteGraph.NODE_TITLE_COLOR;
      -			ctx.fillText( title, 16, 13 - title_height );
      -		}
      -	}
      -}
      -
      -/* Renders the node when collapsed */
      -LGraphCanvas.prototype.drawNodeCollapsed = function(node, ctx, fgcolor, bgcolor)
      -{
      -	//draw default collapsed shape
      -	ctx.strokeStyle = fgcolor || LiteGraph.NODE_DEFAULT_COLOR;
      -	ctx.fillStyle = bgcolor || LiteGraph.NODE_DEFAULT_BGCOLOR;
      -
      -	var collapsed_radius = LiteGraph.NODE_COLLAPSED_RADIUS;
      -
      -	//circle shape
      -	var shape = node._shape || LiteGraph.BOX_SHAPE;
      -	if(shape == LiteGraph.CIRCLE_SHAPE)
      -	{
      -		ctx.beginPath();
      -		ctx.arc(node.size[0] * 0.5, node.size[1] * 0.5, collapsed_radius,0,Math.PI * 2);
      -		ctx.fill();
      -		ctx.shadowColor = "rgba(0,0,0,0)";
      -		ctx.stroke();
      -
      -		ctx.fillStyle = node.boxcolor || LiteGraph.NODE_DEFAULT_BOXCOLOR;
      -		ctx.beginPath();
      -		ctx.arc(node.size[0] * 0.5, node.size[1] * 0.5, collapsed_radius * 0.5,0,Math.PI * 2);
      -		ctx.fill();
      -	}
      -	else if(shape == LiteGraph.ROUND_SHAPE) //rounded box
      -	{
      -		ctx.beginPath();
      -		ctx.roundRect(node.size[0] * 0.5 - collapsed_radius, node.size[1] * 0.5 - collapsed_radius, 2*collapsed_radius,2*collapsed_radius,5);
      -		ctx.fill();
      -		ctx.shadowColor = "rgba(0,0,0,0)";
      -		ctx.stroke();
      -
      -		ctx.fillStyle = node.boxcolor || LiteGraph.NODE_DEFAULT_BOXCOLOR;
      -		ctx.beginPath();
      -		ctx.roundRect(node.size[0] * 0.5 - collapsed_radius*0.5, node.size[1] * 0.5 - collapsed_radius*0.5, collapsed_radius,collapsed_radius,2);
      -		ctx.fill();
      -	}
      -	else //flat box
      -	{
      -		ctx.beginPath();
      -		//ctx.rect(node.size[0] * 0.5 - collapsed_radius, node.size[1] * 0.5 - collapsed_radius, 2*collapsed_radius, 2*collapsed_radius);
      -		ctx.rect(0, 0, node.size[0], collapsed_radius * 2 );
      -		ctx.fill();
      -		ctx.shadowColor = "rgba(0,0,0,0)";
      -		ctx.stroke();
      -
      -		ctx.fillStyle = node.boxcolor || LiteGraph.NODE_DEFAULT_BOXCOLOR;
      -		ctx.beginPath();
      -		//ctx.rect(node.size[0] * 0.5 - collapsed_radius*0.5, node.size[1] * 0.5 - collapsed_radius*0.5, collapsed_radius,collapsed_radius);
      -		ctx.rect(collapsed_radius*0.5, collapsed_radius*0.5, collapsed_radius, collapsed_radius);
      -		ctx.fill();
      -	}
      -}
      -
      -//OPTIMIZE THIS: precatch connections position instead of recomputing them every time
      -LGraphCanvas.prototype.drawConnections = function(ctx)
      -{
      -	var now = LiteGraph.getTime();
      -
      -	//draw connections
      -	ctx.lineWidth = this.connections_width;
      -
      -	ctx.fillStyle = "#AAA";
      -	ctx.strokeStyle = "#AAA";
      -	ctx.globalAlpha = this.editor_alpha;
      -	//for every node
      -	for (var n = 0, l = this.graph._nodes.length; n < l; ++n)
      -	{
      -		var node = this.graph._nodes[n];
      -		//for every input (we render just inputs because it is easier as every slot can only have one input)
      -		if(node.inputs && node.inputs.length)
      -			for(var i = 0; i < node.inputs.length; ++i)
      -			{
      -				var input = node.inputs[i];
      -				if(!input || input.link == null)
      -					continue;
      -				var link_id = input.link;
      -				var link = this.graph.links[ link_id ];
      -				if(!link)
      -					continue;
      -
      -				var start_node = this.graph.getNodeById( link.origin_id );
      -				if(start_node == null) continue;
      -				var start_node_slot = link.origin_slot;
      -				var start_node_slotpos = null;
      -
      -				if(start_node_slot == -1)
      -					start_node_slotpos = [start_node.pos[0] + 10, start_node.pos[1] + 10];
      -				else
      -					start_node_slotpos = start_node.getConnectionPos(false, start_node_slot);
      -
      -				this.renderLink( ctx, start_node_slotpos, node.getConnectionPos(true,i), link );
      -
      -				//event triggered rendered on top
      -				if(link && link._last_time && (now - link._last_time) < 1000 )
      -				{
      -					var f = 2.0 - (now - link._last_time) * 0.002;
      -					var color = "rgba(255,255,255, " + f.toFixed(2) + ")";
      -					this.renderLink( ctx, start_node_slotpos, node.getConnectionPos(true,i), link, true, f, color );
      -				}
      -			}
      -	}
      -	ctx.globalAlpha = 1;
      -}
      -
      -LGraphCanvas.prototype.renderLink = function( ctx, a, b, link, skip_border, flow, color )
      -{
      -	if(!this.highquality_render)
      -	{
      -		ctx.beginPath();
      -		ctx.moveTo(a[0],a[1]);
      -		ctx.lineTo(b[0],b[1]);
      -		ctx.stroke();
      -		return;
      -	}
      -
      -	var dist = distance(a,b);
      -
      -	if(this.render_connections_border && this.scale > 0.6)
      -		ctx.lineWidth = this.connections_width + 4;
      -
      -	//choose color
      -	if( !color && link )
      -		color = LGraphCanvas.link_type_colors[ link.type ];
      -	if(!color)
      -		color = this.default_link_color;
      -
      -	if( link != null && this.highlighted_links[ link.id ] )
      -		color = "#FFF";
      -
      -	//begin line shape
      -	ctx.beginPath();
      -
      -	if(this.render_curved_connections) //splines
      -	{
      -		ctx.moveTo(a[0],a[1]);
      -		ctx.bezierCurveTo(a[0] + dist*0.25, a[1],
      -							b[0] - dist*0.25 , b[1],
      -							b[0] ,b[1] );
      -	}
      -	else //lines
      -	{
      -		ctx.moveTo(a[0]+10,a[1]);
      -		ctx.lineTo(((a[0]+10) + (b[0]-10))*0.5,a[1]);
      -		ctx.lineTo(((a[0]+10) + (b[0]-10))*0.5,b[1]);
      -		ctx.lineTo(b[0]-10,b[1]);
      -	}
      -
      -	//rendering the outline of the connection can be a little bit slow
      -	if(this.render_connections_border && this.scale > 0.6 && !skip_border)
      -	{
      -		ctx.strokeStyle = "rgba(0,0,0,0.5)";
      -		ctx.stroke();
      -	}
      -
      -	ctx.lineWidth = this.connections_width;
      -	ctx.fillStyle = ctx.strokeStyle = color;
      -	ctx.stroke();
      -	//end line shape
      -
      -	//render arrow in the middle
      -	if( this.render_connection_arrows && this.scale >= 0.6 )
      -	{
      -		//render arrow
      -		if(this.render_connection_arrows && this.scale > 0.6)
      -		{
      -			//compute two points in the connection
      -			var pos = this.computeConnectionPoint(a,b,0.5);
      -			var pos2 = this.computeConnectionPoint(a,b,0.51);
      -
      -			//compute the angle between them so the arrow points in the right direction
      -			var angle = 0;
      -			if(this.render_curved_connections)
      -				angle = -Math.atan2( pos2[0] - pos[0], pos2[1] - pos[1]);
      -			else
      -				angle = b[1] > a[1] ? 0 : Math.PI;
      -
      -			//render arrow
      -			ctx.save();
      -			ctx.translate(pos[0],pos[1]);
      -			ctx.rotate(angle);
      -			ctx.beginPath();
      -			ctx.moveTo(-5,-5);
      -			ctx.lineTo(0,+5);
      -			ctx.lineTo(+5,-5);
      -			ctx.fill();
      -			ctx.restore();
      -		}
      -	}
      -
      -	//render flowing points
      -	if(flow)
      -	{
      -		for(var i = 0; i < 5; ++i)
      -		{
      -			var f = (LiteGraph.getTime() * 0.001 + (i * 0.2)) % 1;
      -			var pos = this.computeConnectionPoint(a,b,f);
      -			ctx.beginPath();
      -			ctx.arc(pos[0],pos[1],5,0,2*Math.PI);
      -			ctx.fill();
      -		}
      -	}
      -}
      -
      -LGraphCanvas.prototype.computeConnectionPoint = function(a,b,t)
      -{
      -	var dist = distance(a,b);
      -	var p0 = a;
      -	var p1 = [ a[0] + dist*0.25, a[1] ];
      -	var p2 = [ b[0] - dist*0.25, b[1] ];
      -	var p3 = b;
      -
      -	var c1 = (1-t)*(1-t)*(1-t);
      -	var c2 = 3*((1-t)*(1-t))*t;
      -	var c3 = 3*(1-t)*(t*t);
      -	var c4 = t*t*t;
      -
      -	var x = c1*p0[0] + c2*p1[0] + c3*p2[0] + c4*p3[0];
      -	var y = c1*p0[1] + c2*p1[1] + c3*p2[1] + c4*p3[1];
      -	return [x,y];
      -}
      -
      -/*
      -LGraphCanvas.prototype.resizeCanvas = function(width,height)
      -{
      -	this.canvas.width = width;
      -	if(height)
      -		this.canvas.height = height;
      -
      -	this.bgcanvas.width = this.canvas.width;
      -	this.bgcanvas.height = this.canvas.height;
      -	this.draw(true,true);
      -}
      -*/
      -
      -LGraphCanvas.prototype.resize = function(width, height)
      -{
      -	if(!width && !height)
      -	{
      -		var parent = this.canvas.parentNode;
      -		width = parent.offsetWidth;
      -		height = parent.offsetHeight;
      -	}
      -
      -	if(this.canvas.width == width && this.canvas.height == height)
      -		return;
      -
      -	this.canvas.width = width;
      -	this.canvas.height = height;
      -	this.bgcanvas.width = this.canvas.width;
      -	this.bgcanvas.height = this.canvas.height;
      -	this.setDirty(true,true);
      -}
      -
      -
      -LGraphCanvas.prototype.switchLiveMode = function(transition)
      -{
      -	if(!transition)
      -	{
      -		this.live_mode = !this.live_mode;
      -		this.dirty_canvas = true;
      -		this.dirty_bgcanvas = true;
      -		return;
      -	}
      -
      -	var self = this;
      -	var delta = this.live_mode ? 1.1 : 0.9;
      -	if(this.live_mode)
      -	{
      -		this.live_mode = false;
      -		this.editor_alpha = 0.1;
      -	}
      -
      -	var t = setInterval(function() {
      -		self.editor_alpha *= delta;
      -		self.dirty_canvas = true;
      -		self.dirty_bgcanvas = true;
      -
      -		if(delta < 1  && self.editor_alpha < 0.01)
      -		{
      -			clearInterval(t);
      -			if(delta < 1)
      -				self.live_mode = true;
      -		}
      -		if(delta > 1 && self.editor_alpha > 0.99)
      -		{
      -			clearInterval(t);
      -			self.editor_alpha = 1;
      -		}
      -	},1);
      -}
      -
      -LGraphCanvas.prototype.onNodeSelectionChange = function(node)
      -{
      -	return; //disabled
      -	//if(this.node_in_panel) this.showNodePanel(node);
      -}
      -
      -LGraphCanvas.prototype.touchHandler = function(event)
      -{
      -	//alert("foo");
      -    var touches = event.changedTouches,
      -        first = touches[0],
      -        type = "";
      -
      -         switch(event.type)
      -    {
      -        case "touchstart": type = "mousedown"; break;
      -        case "touchmove":  type="mousemove"; break;
      -        case "touchend":   type="mouseup"; break;
      -        default: return;
      -    }
      -
      -             //initMouseEvent(type, canBubble, cancelable, view, clickCount,
      -    //           screenX, screenY, clientX, clientY, ctrlKey,
      -    //           altKey, shiftKey, metaKey, button, relatedTarget);
      -
      -	var window = this.getCanvasWindow();
      -	var document = window.document;
      -
      -    var simulatedEvent = document.createEvent("MouseEvent");
      -    simulatedEvent.initMouseEvent(type, true, true, window, 1,
      -                              first.screenX, first.screenY,
      -                              first.clientX, first.clientY, false,
      -                              false, false, false, 0/*left*/, null);
      -	first.target.dispatchEvent(simulatedEvent);
      -    event.preventDefault();
      -}
      -
      -/* CONTEXT MENU ********************/
      -
      -LGraphCanvas.onMenuAdd = function( node, options, e, prev_menu )
      -{
      -	var canvas = LGraphCanvas.active_canvas;
      -	var ref_window = canvas.getCanvasWindow();
      -
      -	var values = LiteGraph.getNodeTypesCategories();
      -	var entries = [];
      -	for(var i in values)
      -		if(values[i])
      -			entries.push({ value: values[i], content: values[i], has_submenu: true });
      -
      -	var menu = new LiteGraph.ContextMenu( entries, { event: e, callback: inner_clicked, parentMenu: prev_menu }, ref_window);
      -
      -	function inner_clicked( v, option, e )
      -	{
      -		var category = v.value;
      -		var node_types = LiteGraph.getNodeTypesInCategory(category);
      -		var values = [];
      -		for(var i in node_types)
      -			values.push( { content: node_types[i].title, value: node_types[i].type });
      -
      -		new LiteGraph.ContextMenu( values, {event: e, callback: inner_create, parentMenu: menu }, ref_window);
      -		return false;
      -	}
      -
      -	function inner_create( v, e )
      -	{
      -		var first_event = prev_menu.getFirstEvent();
      -		var node = LiteGraph.createNode( v.value );
      -		if(node)
      -		{
      -			node.pos = canvas.convertEventToCanvas( first_event );
      -			canvas.graph.add( node );
      -		}
      -	}
      -
      -	return false;
      -}
      -
      -LGraphCanvas.onMenuCollapseAll = function()
      -{
      -
      -}
      -
      -
      -LGraphCanvas.onMenuNodeEdit = function()
      -{
      -
      -}
      -
      -LGraphCanvas.showMenuNodeOptionalInputs = function( v, options, e, prev_menu, node )
      -{
      -	if(!node)
      -		return;
      -
      -	var that = this;
      -	var canvas = LGraphCanvas.active_canvas;
      -	var ref_window = canvas.getCanvasWindow();
      -
      -	var options = node.optional_inputs;
      -	if(node.onGetInputs)
      -		options = node.onGetInputs();
      -
      -	var entries = [];
      -	if(options)
      -		for (var i in options)
      -		{
      -			var entry = options[i];
      -			if(!entry)
      -			{
      -				entries.push(null);
      -				continue;
      -			}
      -			var label = entry[0];
      -			if(entry[2] && entry[2].label)
      -				label = entry[2].label;
      -			var data = {content: label, value: entry};
      -			if(entry[1] == LiteGraph.ACTION)
      -				data.className = "event";
      -			entries.push(data);
      -		}
      -
      -	if(this.onMenuNodeInputs)
      -		entries = this.onMenuNodeInputs( entries );
      -
      -	if(!entries.length)
      -		return;
      -
      -	var menu = new LiteGraph.ContextMenu(entries, { event: e, callback: inner_clicked, parentMenu: prev_menu, node: node }, ref_window);
      -
      -	function inner_clicked(v, e, prev)
      -	{
      -		if(!node)
      -			return;
      -
      -		if(v.callback)
      -			v.callback.call( that, node, v, e, prev );
      -
      -		if(v.value)
      -		{
      -			node.addInput(v.value[0],v.value[1], v.value[2]);
      -			node.setDirtyCanvas(true,true);
      -		}
      -	}
      -
      -	return false;
      -}
      -
      -LGraphCanvas.showMenuNodeOptionalOutputs = function( v, options, e, prev_menu, node )
      -{
      -	if(!node)
      -		return;
      -
      -	var that = this;
      -	var canvas = LGraphCanvas.active_canvas;
      -	var ref_window = canvas.getCanvasWindow();
      -
      -	var options = node.optional_outputs;
      -	if(node.onGetOutputs)
      -		options = node.onGetOutputs();
      -
      -	var entries = [];
      -	if(options)
      -		for (var i in options)
      -		{
      -			var entry = options[i];
      -			if(!entry) //separator?
      -			{
      -				entries.push(null);
      -				continue;
      -			}
      -
      -			if(node.flags && node.flags.skip_repeated_outputs && node.findOutputSlot(entry[0]) != -1)
      -				continue; //skip the ones already on
      -			var label = entry[0];
      -			if(entry[2] && entry[2].label)
      -				label = entry[2].label;
      -			var data = {content: label, value: entry};
      -			if(entry[1] == LiteGraph.EVENT)
      -				data.className = "event";
      -			entries.push(data);
      -		}
      -
      -	if(this.onMenuNodeOutputs)
      -		entries = this.onMenuNodeOutputs( entries );
      -
      -	if(!entries.length)
      -		return;
      -
      -	var menu = new LiteGraph.ContextMenu(entries, {event: e, callback: inner_clicked, parentMenu: prev_menu, node: node }, ref_window);
      -
      -	function inner_clicked( v, e, prev )
      -	{
      -		if(!node)
      -			return;
      -
      -		if(v.callback)
      -			v.callback.call( that, node, v, e, prev );
      -
      -		if(!v.value)
      -			return;
      -
      -		var value = v.value[1];
      -
      -		if(value && (value.constructor === Object || value.constructor === Array)) //submenu why?
      -		{
      -			var entries = [];
      -			for(var i in value)
      -				entries.push({ content: i, value: value[i]});
      -			new LiteGraph.ContextMenu( entries, { event: e, callback: inner_clicked, parentMenu: prev_menu, node: node });
      -			return false;
      -		}
      -		else
      -		{
      -			node.addOutput( v.value[0], v.value[1], v.value[2]);
      -			node.setDirtyCanvas(true,true);
      -		}
      -
      -	}
      -
      -	return false;
      -}
      -
      -LGraphCanvas.onShowMenuNodeProperties = function( value, options, e, prev_menu, node )
      -{
      -	if(!node || !node.properties)
      -		return;
      -
      -	var that = this;
      -	var canvas = LGraphCanvas.active_canvas;
      -	var ref_window = canvas.getCanvasWindow();
      -
      -	var entries = [];
      -		for (var i in node.properties)
      -		{
      -			var value = node.properties[i] !== undefined ? node.properties[i] : " ";
      -			//value could contain invalid html characters, clean that
      -			value = LGraphCanvas.decodeHTML(value);
      -			entries.push({content: "<span class='property_name'>" + i + "</span>" + "<span class='property_value'>" + value + "</span>", value: i});
      -		}
      -	if(!entries.length)
      -		return;
      -
      -	var menu = new LiteGraph.ContextMenu(entries, {event: e, callback: inner_clicked, parentMenu: prev_menu, allow_html: true, node: node },ref_window);
      -
      -	function inner_clicked( v, options, e, prev )
      -	{
      -		if(!node)
      -			return;
      -		var rect = this.getBoundingClientRect();
      -		canvas.showEditPropertyValue( node, v.value, { position: [rect.left, rect.top] });
      -	}
      -
      -	return false;
      -}
      -
      -LGraphCanvas.decodeHTML = function( str )
      -{
      -	var e = document.createElement("div");
      -	e.innerText = str;
      -	return e.innerHTML;
      -}
      -
      -LGraphCanvas.onResizeNode = function( value, options, e, menu, node )
      -{
      -	if(!node)
      -		return;
      -	node.size = node.computeSize();
      -	node.setDirtyCanvas(true,true);
      -}
      -
      -
      -LGraphCanvas.onShowTitleEditor = function( value, options, e, menu, node )
      -{
      -	var input_html = "";
      -
      -	var dialog = document.createElement("div");
      -	dialog.className = "graphdialog";
      -	dialog.innerHTML = "<span class='name'>Title</span><input autofocus type='text' class='value'/><button>OK</button>";
      -	var input = dialog.querySelector("input");
      -	if(input)
      -	{
      -		input.value = node.title;
      -		input.addEventListener("keydown", function(e){
      -			if(e.keyCode != 13)
      -				return;
      -			inner();
      -			e.preventDefault();
      -			e.stopPropagation();
      -		});
      -	}
      -
      -	var graphcanvas = LGraphCanvas.active_canvas;
      -	var canvas = graphcanvas.canvas;
      -
      -	var rect = canvas.getBoundingClientRect();
      -	var offsetx = -20;
      -	var offsety = -20;
      -	if(rect)
      -	{
      -		offsetx -= rect.left;
      -		offsety -= rect.top;
      -	}
      -
      -	if( event )
      -	{
      -		dialog.style.left = (event.pageX + offsetx) + "px";
      -		dialog.style.top = (event.pageY + offsety)+ "px";
      -	}
      -	else
      -	{
      -		dialog.style.left = (canvas.width * 0.5 + offsetx) + "px";
      -		dialog.style.top = (canvas.height * 0.5 + offsety) + "px";
      -	}
      -
      -	var button = dialog.querySelector("button");
      -	button.addEventListener("click", inner );
      -	canvas.parentNode.appendChild( dialog );
      -
      -	function inner()
      -	{
      -		setValue( input.value );
      -	}
      -
      -	function setValue(value)
      -	{
      -		node.title = value;
      -		dialog.parentNode.removeChild( dialog );
      -		node.setDirtyCanvas(true,true);
      -	}
      -}
      -
      -LGraphCanvas.prototype.showEditPropertyValue = function( node, property, options )
      -{
      -	if(!node || node.properties[ property ] === undefined )
      -		return;
      -
      -	options = options || {};
      -	var that = this;
      -
      -	var type = "string";
      -
      -	if(node.properties[ property ] !== null)
      -		type = typeof(node.properties[ property ]);
      -
      -	var info = null;
      -	if(node.getPropertyInfo)
      -		info = node.getPropertyInfo(property);
      -	if(node.properties_info)
      -	{
      -		for(var i = 0; i < node.properties_info.length; ++i)
      -		{
      -			if( node.properties_info[i].name == property )
      -			{
      -				info = node.properties_info[i];
      -				break;
      -			}
      -		}
      -	}
      -
      -	if(info !== undefined && info !== null && info.type )
      -		type = info.type;
      -
      -	var input_html = "";
      -
      -	if(type == "string" || type == "number")
      -		input_html = "<input autofocus type='text' class='value'/>";
      -	else if(type == "enum" && info.values)
      -	{
      -		input_html = "<select autofocus type='text' class='value'>";
      -		for(var i in info.values)
      -		{
      -			var v = info.values.constructor === Array ? info.values[i] : i;
      -			input_html += "<option value='"+v+"' "+(v == node.properties[property] ? "selected" : "")+">"+info.values[i]+"</option>";
      -		}
      -		input_html += "</select>";
      -	}
      -	else if(type == "boolean")
      -	{
      -		input_html = "<input autofocus type='checkbox' class='value' "+(node.properties[property] ? "checked" : "")+"/>";
      -	}
      -
      -	var dialog = this.createDialog( "<span class='name'>" + property + "</span>"+input_html+"<button>OK</button>" , options );
      -
      -	if(type == "enum" && info.values)
      -	{
      -		var input = dialog.querySelector("select");
      -		input.addEventListener("change", function(e){
      -			setValue( e.target.value );
      -			//var index = e.target.value;
      -			//setValue( e.options[e.selectedIndex].value );
      -		});
      -	}
      -	else if(type == "boolean")
      -	{
      -		var input = dialog.querySelector("input");
      -		if(input)
      -		{
      -			input.addEventListener("click", function(e){
      -				setValue( !!input.checked );
      -			});
      -		}
      -	}
      -	else
      -	{
      -		var input = dialog.querySelector("input");
      -		if(input)
      -		{
      -			input.value = node.properties[ property ] !== undefined ? node.properties[ property ] : "";
      -			input.addEventListener("keydown", function(e){
      -				if(e.keyCode != 13)
      -					return;
      -				inner();
      -				e.preventDefault();
      -				e.stopPropagation();
      -			});
      -		}
      -	}
      -
      -	var button = dialog.querySelector("button");
      -	button.addEventListener("click", inner );
      -
      -	function inner()
      -	{
      -		setValue( input.value );
      -	}
      -
      -	function setValue(value)
      -	{
      -		if(typeof( node.properties[ property ] ) == "number")
      -			value = Number(value);
      -		node.properties[ property ] = value;
      -
      -		if(node.onPropertyChanged)
      -			node.onPropertyChanged( property, value );
      -		dialog.close();
      -		node.setDirtyCanvas(true,true);
      -	}
      -}
      -
      -LGraphCanvas.prototype.createDialog = function( html, options )
      -{
      -	options = options || {};
      -
      -	var dialog = document.createElement("div");
      -	dialog.className = "graphdialog";
      -	dialog.innerHTML = html;
      -
      -	var rect = this.canvas.getBoundingClientRect();
      -	var offsetx = -20;
      -	var offsety = -20;
      -	if(rect)
      -	{
      -		offsetx -= rect.left;
      -		offsety -= rect.top;
      -	}
      -
      -	if( options.position )
      -	{
      -		offsetx += options.position[0];
      -		offsety += options.position[1];
      -	}
      -	else if( options.event )
      -	{
      -		offsetx += options.event.pageX;
      -		offsety += options.event.pageY;
      -	}
      -	else //centered
      -	{
      -		offsetx += this.canvas.width * 0.5;
      -		offsety += this.canvas.height * 0.5;
      -	}
      -
      -	dialog.style.left = offsetx + "px";
      -	dialog.style.top = offsety + "px";
      -
      -	this.canvas.parentNode.appendChild( dialog );
      -
      -	dialog.close = function()
      -	{
      -		if(this.parentNode)
      -			this.parentNode.removeChild( this );
      -	}
      -
      -	return dialog;
      -}
      -
      -LGraphCanvas.onMenuNodeCollapse = function( value, options, e, menu, node )
      -{
      -	node.flags.collapsed = !node.flags.collapsed;
      -	node.setDirtyCanvas(true,true);
      -}
      -
      -LGraphCanvas.onMenuNodePin = function( value, options, e, menu, node )
      -{
      -	node.pin();
      -}
      -
      -LGraphCanvas.onMenuNodeMode = function( value, options, e, menu, node )
      -{
      -	new LiteGraph.ContextMenu(["Always","On Event","On Trigger","Never"], {event: e, callback: inner_clicked, parentMenu: menu, node: node });
      -
      -	function inner_clicked(v)
      -	{
      -		if(!node)
      -			return;
      -		switch(v)
      -		{
      -			case "On Event": node.mode = LiteGraph.ON_EVENT; break;
      -			case "On Trigger": node.mode = LiteGraph.ON_TRIGGER; break;
      -			case "Never": node.mode = LiteGraph.NEVER; break;
      -			case "Always":
      -			default:
      -				node.mode = LiteGraph.ALWAYS; break;
      -		}
      -	}
      -
      -	return false;
      -}
      -
      -LGraphCanvas.onMenuNodeColors = function( value, options, e, menu, node )
      -{
      -	if(!node)
      -		throw("no node for color");
      -
      -	var values = [];
      -	for(var i in LGraphCanvas.node_colors)
      -	{
      -		var color = LGraphCanvas.node_colors[i];
      -		var value = {value:i, content:"<span style='display: block; color:"+color.color+"; background-color:"+color.bgcolor+"'>"+i+"</span>"};
      -		values.push(value);
      -	}
      -	new LiteGraph.ContextMenu( values, { event: e, callback: inner_clicked, parentMenu: menu, node: node });
      -
      -	function inner_clicked(v)
      -	{
      -		if(!node)
      -			return;
      -
      -		var color = LGraphCanvas.node_colors[ v.value ];
      -		if(color)
      -		{
      -			node.color = color.color;
      -			node.bgcolor = color.bgcolor;
      -			node.setDirtyCanvas(true);
      -		}
      -	}
      -
      -	return false;
      -}
      -
      -LGraphCanvas.onMenuNodeShapes = function( value, options, e, menu, node )
      -{
      -	if(!node)
      -		throw("no node passed");
      -
      -	new LiteGraph.ContextMenu( LiteGraph.VALID_SHAPES, { event: e, callback: inner_clicked, parentMenu: menu, node: node });
      -
      -	function inner_clicked(v)
      -	{
      -		if(!node)
      -			return;
      -		node.shape = v;
      -		node.setDirtyCanvas(true);
      -	}
      -
      -	return false;
      -}
      -
      -LGraphCanvas.onMenuNodeRemove = function( value, options, e, menu, node )
      -{
      -	if(!node)
      -		throw("no node passed");
      -
      -	if(node.removable == false)
      -		return;
      -	node.graph.remove(node);
      -	node.setDirtyCanvas(true,true);
      -}
      -
      -LGraphCanvas.onMenuNodeClone = function( value, options, e, menu, node )
      -{
      -	if(node.clonable == false) return;
      -	var newnode = node.clone();
      -	if(!newnode)
      -		return;
      -	newnode.pos = [node.pos[0]+5,node.pos[1]+5];
      -	node.graph.add(newnode);
      -	node.setDirtyCanvas(true,true);
      -}
      -
      -LGraphCanvas.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" }
      -};
      -
      -LGraphCanvas.prototype.getCanvasMenuOptions = function()
      -{
      -	var options = null;
      -	if(this.getMenuOptions)
      -		options = this.getMenuOptions();
      -	else
      -	{
      -		options = [
      -			{ content:"Add Node", has_submenu: true, callback: LGraphCanvas.onMenuAdd }
      -			//{content:"Collapse All", callback: LGraphCanvas.onMenuCollapseAll }
      -		];
      -
      -		if(this._graph_stack && this._graph_stack.length > 0)
      -			options = [{content:"Close subgraph", callback: this.closeSubgraph.bind(this) },null].concat(options);
      -	}
      -
      -	if(this.getExtraMenuOptions)
      -	{
      -		var extra = this.getExtraMenuOptions(this,options);
      -		if(extra)
      -			options = options.concat( extra );
      -	}
      -
      -	return options;
      -}
      -
      -//called by processContextMenu to extract the menu list
      -LGraphCanvas.prototype.getNodeMenuOptions = function( node )
      -{
      -	var options = null;
      -
      -	if(node.getMenuOptions)
      -		options = node.getMenuOptions(this);
      -	else
      -		options = [
      -			{content:"Inputs", has_submenu: true, disabled:true, callback: LGraphCanvas.showMenuNodeOptionalInputs },
      -			{content:"Outputs", has_submenu: true, disabled:true, callback: LGraphCanvas.showMenuNodeOptionalOutputs },
      -			null,
      -			{content:"Properties", has_submenu: true, callback: LGraphCanvas.onShowMenuNodeProperties },
      -			null,
      -			{content:"Title", callback: LGraphCanvas.onShowTitleEditor },
      -			{content:"Mode", has_submenu: true, callback: LGraphCanvas.onMenuNodeMode },
      -			{content:"Resize", callback: LGraphCanvas.onResizeNode },
      -			{content:"Collapse", callback: LGraphCanvas.onMenuNodeCollapse },
      -			{content:"Pin", callback: LGraphCanvas.onMenuNodePin },
      -			{content:"Colors", has_submenu: true, callback: LGraphCanvas.onMenuNodeColors },
      -			{content:"Shapes", has_submenu: true, callback: LGraphCanvas.onMenuNodeShapes },
      -			null
      -		];
      -
      -	if(node.getExtraMenuOptions)
      -	{
      -		var extra = node.getExtraMenuOptions(this);
      -		if(extra)
      -		{
      -			extra.push(null);
      -			options = extra.concat( options );
      -		}
      -	}
      -
      -	if( node.clonable !== false )
      -			options.push({content:"Clone", callback: LGraphCanvas.onMenuNodeClone });
      -	if( node.removable !== false )
      -			options.push(null,{content:"Remove", callback: LGraphCanvas.onMenuNodeRemove });
      -
      -	if(node.onGetInputs)
      -	{
      -		var inputs = node.onGetInputs();
      -		if(inputs && inputs.length)
      -			options[0].disabled = false;
      -	}
      -
      -	if(node.onGetOutputs)
      -	{
      -		var outputs = node.onGetOutputs();
      -		if(outputs && outputs.length )
      -			options[1].disabled = false;
      -	}
      -
      -	if(node.graph && node.graph.onGetNodeMenuOptions )
      -		node.graph.onGetNodeMenuOptions( options, node );
      -
      -	return options;
      -}
      -
      -LGraphCanvas.prototype.processContextMenu = function( node, event )
      -{
      -	var that = this;
      -	var canvas = LGraphCanvas.active_canvas;
      -	var ref_window = canvas.getCanvasWindow();
      -
      -	var menu_info = null;
      -	var options = { event: event, callback: inner_option_clicked, node: node };
      -
      -	//check if mouse is in input
      -	var slot = null;
      -	if(node)
      -	{
      -		slot = node.getSlotInPosition( event.canvasX, event.canvasY );
      -		LGraphCanvas.active_node = node;
      -	}
      -
      -	if(slot)
      -	{
      -		menu_info = [];
      -		menu_info.push( slot.locked ? "Cannot remove"  : { content: "Remove Slot", slot: slot } );
      -		menu_info.push( { content: "Rename Slot", slot: slot } );
      -		options.title = (slot.input ? slot.input.type : slot.output.type) || "*";
      -		if(slot.input && slot.input.type == LiteGraph.ACTION)
      -			options.title = "Action";
      -		if(slot.output && slot.output.type == LiteGraph.EVENT)
      -			options.title = "Event";
      -
      -	}
      -	else
      -		menu_info = node ? this.getNodeMenuOptions(node) : this.getCanvasMenuOptions();
      -
      -	//show menu
      -	if(!menu_info)
      -		return;
      -
      -	var menu = new LiteGraph.ContextMenu( menu_info, options, ref_window );
      -
      -	function inner_option_clicked( v, options, e )
      -	{
      -		if(!v)
      -			return;
      -
      -		if(v.content == "Remove Slot")
      -		{
      -			var info = v.slot;
      -			if(info.input)
      -				node.removeInput( info.slot );
      -			else if(info.output)
      -				node.removeOutput( info.slot );
      -			return;
      -		}
      -		else if( v.content == "Rename Slot")
      -		{
      -			var info = v.slot;
      -			var dialog = that.createDialog( "<span class='name'>Name</span><input type='text'/><button>OK</button>" , options );
      -			var input = dialog.querySelector("input");
      -			dialog.querySelector("button").addEventListener("click",function(e){
      -				if(input.value)
      -				{
      -					var slot_info = info.input ? node.getInputInfo( info.slot ) : node.getOutputInfo( info.slot );
      -					if( slot_info )
      -						slot_info.label = input.value;
      -					that.setDirty(true);
      -				}
      -				dialog.close();
      -			});
      -		}
      -
      -		//if(v.callback)
      -		//	return v.callback.call(that, node, options, e, menu, that, event );
      -	}
      -}
      -
      -
      -
      -
      -
      -
      -//API *************************************************
      -//function roundRect(ctx, x, y, width, height, radius, radius_low) {
      -if(this.CanvasRenderingContext2D)
      -CanvasRenderingContext2D.prototype.roundRect = function (x, y, width, height, radius, radius_low) {
      -  if ( radius === undefined ) {
      -    radius = 5;
      -  }
      -
      -  if(radius_low === undefined)
      -	 radius_low  = radius;
      -
      -  this.beginPath();
      -  this.moveTo(x + radius, y);
      -  this.lineTo(x + width - radius, y);
      -  this.quadraticCurveTo(x + width, y, x + width, y + radius);
      -
      -  this.lineTo(x + width, y + height - radius_low);
      -  this.quadraticCurveTo(x + width, y + height, x + width - radius_low, y + height);
      -  this.lineTo(x + radius_low, y + height);
      -  this.quadraticCurveTo(x, y + height, x, y + height - radius_low);
      -  this.lineTo(x, y + radius);
      -  this.quadraticCurveTo(x, y, x + radius, y);
      -}
      -
      -function compareObjects(a,b)
      -{
      -	for(var i in a)
      -		if(a[i] != b[i])
      -			return false;
      -	return true;
      -}
      -LiteGraph.compareObjects = compareObjects;
      -
      -function distance(a,b)
      -{
      -	return Math.sqrt( (b[0] - a[0]) * (b[0] - a[0]) + (b[1] - a[1]) * (b[1] - a[1]) );
      -}
      -LiteGraph.distance = distance;
      -
      -function colorToString(c)
      -{
      -	return "rgba(" + Math.round(c[0] * 255).toFixed() + "," + Math.round(c[1] * 255).toFixed() + "," + Math.round(c[2] * 255).toFixed() + "," + (c.length == 4 ? c[3].toFixed(2) : "1.0") + ")";
      -}
      -LiteGraph.colorToString = colorToString;
      -
      -function isInsideRectangle( x,y, left, top, width, height)
      -{
      -	if (left < x && (left + width) > x &&
      -		top < y && (top + height) > y)
      -		return true;
      -	return false;
      -}
      -LiteGraph.isInsideRectangle = isInsideRectangle;
      -
      -//[minx,miny,maxx,maxy]
      -function growBounding( bounding, x,y)
      -{
      -	if(x < bounding[0])
      -		bounding[0] = x;
      -	else if(x > bounding[2])
      -		bounding[2] = x;
      -
      -	if(y < bounding[1])
      -		bounding[1] = y;
      -	else if(y > bounding[3])
      -		bounding[3] = y;
      -}
      -LiteGraph.growBounding = growBounding;
      -
      -//point inside boundin box
      -function isInsideBounding(p,bb)
      -{
      -	if (p[0] < bb[0][0] ||
      -		p[1] < bb[0][1] ||
      -		p[0] > bb[1][0] ||
      -		p[1] > bb[1][1])
      -		return false;
      -	return true;
      -}
      -LiteGraph.isInsideBounding = isInsideBounding;
      -
      -//boundings overlap, format: [ startx, starty, width, height ]
      -function overlapBounding(a,b)
      -{
      -	var A_end_x = a[0] + a[2];
      -	var A_end_y = a[1] + a[3];
      -	var B_end_x = b[0] + b[2];
      -	var B_end_y = b[1] + b[3];
      -
      -	if ( a[0] > B_end_x ||
      -		a[1] > B_end_y ||
      -		A_end_x < b[0] ||
      -		A_end_y < b[1])
      -		return false;
      -	return true;
      -}
      -LiteGraph.overlapBounding = overlapBounding;
      -
      -//Convert a hex value to its decimal value - the inputted hex must be in the
      -//	format of a hex triplet - the kind we use for HTML colours. The function
      -//	will return an array with three values.
      -function hex2num(hex) {
      -	if(hex.charAt(0) == "#") hex = hex.slice(1); //Remove the '#' char - if there is one.
      -	hex = hex.toUpperCase();
      -	var hex_alphabets = "0123456789ABCDEF";
      -	var value = new Array(3);
      -	var k = 0;
      -	var int1,int2;
      -	for(var i=0;i<6;i+=2) {
      -		int1 = hex_alphabets.indexOf(hex.charAt(i));
      -		int2 = hex_alphabets.indexOf(hex.charAt(i+1));
      -		value[k] = (int1 * 16) + int2;
      -		k++;
      -	}
      -	return(value);
      -}
      -
      -LiteGraph.hex2num = hex2num;
      -
      -//Give a array with three values as the argument and the function will return
      -//	the corresponding hex triplet.
      -function num2hex(triplet) {
      -	var hex_alphabets = "0123456789ABCDEF";
      -	var hex = "#";
      -	var int1,int2;
      -	for(var i=0;i<3;i++) {
      -		int1 = triplet[i] / 16;
      -		int2 = triplet[i] % 16;
      -
      -		hex += hex_alphabets.charAt(int1) + hex_alphabets.charAt(int2);
      -	}
      -	return(hex);
      -}
      -
      -LiteGraph.num2hex = num2hex;
      -
      -/* LiteGraph GUI elements used for canvas editing *************************************/
      -
      -/**
      -* ContextMenu from LiteGUI
      -*
      -* @class ContextMenu
      -* @constructor
      -* @param {Array} values (allows object { title: "Nice text", callback: function ... })
      -* @param {Object} options [optional] Some options:\
      -* - title: title to show on top of the menu
      -* - callback: function to call when an option is clicked, it receives the item information
      -* - ignore_item_callbacks: ignores the callback inside the item, it just calls the options.callback
      -* - event: you can pass a MouseEvent, this way the ContextMenu appears in that position
      -*/
      -function ContextMenu( values, options )
      -{
      -	options = options || {};
      -	this.options = options;
      -	var that = this;
      -
      -	//to link a menu with its parent
      -	if(options.parentMenu)
      -	{
      -		if( options.parentMenu.constructor !== this.constructor )
      -		{
      -			console.error("parentMenu must be of class ContextMenu, ignoring it");
      -			options.parentMenu = null;
      -		}
      -		else
      -		{
      -			this.parentMenu = options.parentMenu;
      -			this.parentMenu.lock = true;
      -			this.parentMenu.current_submenu = this;
      -		}
      -	}
      -
      -	if(options.event && options.event.constructor !== MouseEvent && options.event.constructor !== CustomEvent)
      -	{
      -		console.error("Event passed to ContextMenu is not of type MouseEvent or CustomEvent. Ignoring it.");
      -		options.event = null;
      -	}
      -
      -	var root = document.createElement("div");
      -	root.className = "litegraph litecontextmenu litemenubar-panel";
      -	root.style.minWidth = 100;
      -	root.style.minHeight = 100;
      -	root.style.pointerEvents = "none";
      -	setTimeout( function() { root.style.pointerEvents = "auto"; },100); //delay so the mouse up event is not caugh by this element
      -
      -	//this prevents the default context browser menu to open in case this menu was created when pressing right button
      -	root.addEventListener("mouseup", function(e){
      -		e.preventDefault(); return true;
      -	}, true);
      -	root.addEventListener("contextmenu", function(e) {
      -		if(e.button != 2) //right button
      -			return false;
      -		e.preventDefault();
      -		return false;
      -	},true);
      -
      -	root.addEventListener("mousedown", function(e){
      -		if(e.button == 2)
      -		{
      -			that.close();
      -			e.preventDefault(); return true;
      -		}
      -	}, true);
      -
      -
      -	this.root = root;
      -
      -	//title
      -	if(options.title)
      -	{
      -		var element = document.createElement("div");
      -		element.className = "litemenu-title";
      -		element.innerHTML = options.title;
      -		root.appendChild(element);
      -	}
      -
      -	//entries
      -	var num = 0;
      -	for(var i in values)
      -	{
      -		var name = values.constructor == Array ? values[i] : i;
      -		if( name != null && name.constructor !== String )
      -			name = name.content === undefined ? String(name) : name.content;
      -		var value = values[i];
      -		this.addItem( name, value, options );
      -		num++;
      -	}
      -
      -	//close on leave
      -	root.addEventListener("mouseleave", function(e) {
      -		if(that.lock)
      -			return;
      -		that.close(e);
      -	});
      -
      -	//insert before checking position
      -	var root_document = document;
      -	if(options.event)
      -		root_document = options.event.target.ownerDocument;
      -
      -	if(!root_document)
      -		root_document = document;
      -	root_document.body.appendChild(root);
      -
      -	//compute best position
      -	var left = options.left || 0;
      -	var top = options.top || 0;
      -	if(options.event)
      -	{
      -		left = (options.event.pageX - 10);
      -		top = (options.event.pageY - 10);
      -		if(options.title)
      -			top -= 20;
      -
      -		if(options.parentMenu)
      -		{
      -			var rect = options.parentMenu.root.getBoundingClientRect();
      -			left = rect.left + rect.width;
      -		}
      -
      -		var body_rect = document.body.getBoundingClientRect();
      -		var root_rect = root.getBoundingClientRect();
      -
      -		if(left > (body_rect.width - root_rect.width - 10))
      -			left = (body_rect.width - root_rect.width - 10);
      -		if(top > (body_rect.height - root_rect.height - 10))
      -			top = (body_rect.height - root_rect.height - 10);
      -	}
      -
      -	root.style.left = left + "px";
      -	root.style.top = top  + "px";
      -}
      -
      -ContextMenu.prototype.addItem = function( name, value, options )
      -{
      -	var that = this;
      -	options = options || {};
      -
      -	var element = document.createElement("div");
      -	element.className = "litemenu-entry submenu";
      -
      -	var disabled = false;
      -
      -	if(value === null)
      -	{
      -		element.classList.add("separator");
      -		//element.innerHTML = "<hr/>"
      -		//continue;
      -	}
      -	else
      -	{
      -		element.innerHTML = value && value.title ? value.title : name;
      -		element.value = value;
      -
      -		if(value)
      -		{
      -			if(value.disabled)
      -			{
      -				disabled = true;
      -				element.classList.add("disabled");
      -			}
      -			if(value.submenu || value.has_submenu)
      -				element.classList.add("has_submenu");
      -		}
      -
      -		if(typeof(value) == "function")
      -		{
      -			element.dataset["value"] = name;
      -			element.onclick_callback = value;
      -		}
      -		else
      -			element.dataset["value"] = value;
      -
      -		if(value.className)
      -			element.className += " " + value.className;
      -	}
      -
      -	this.root.appendChild(element);
      -	if(!disabled)
      -		element.addEventListener("click", inner_onclick);
      -	if(options.autoopen)
      -		element.addEventListener("mouseenter", inner_over);
      -
      -	function inner_over(e)
      -	{
      -		var value = this.value;
      -		if(!value || !value.has_submenu)
      -			return;
      -		inner_onclick.call(this,e);
      -	}
      -
      -	//menu option clicked
      -	function inner_onclick(e) {
      -		var value = this.value;
      -		var close_parent = true;
      -
      -		if(that.current_submenu)
      -			that.current_submenu.close(e);
      -
      -		//global callback
      -		if(options.callback)
      -		{
      -			var r = options.callback.call( this, value, options, e, that, options.node );
      -			if(r === true)
      -				close_parent = false;
      -		}
      -
      -		//special cases
      -		if(value)
      -		{
      -			if (value.callback && !options.ignore_item_callbacks && value.disabled !== true )  //item callback
      -			{
      -				var r = value.callback.call( this, value, options, e, that, options.node );
      -				if(r === true)
      -					close_parent = false;
      -			}
      -			if(value.submenu)
      -			{
      -				if(!value.submenu.options)
      -					throw("ContextMenu submenu needs options");
      -				var submenu = new that.constructor( value.submenu.options, {
      -					callback: value.submenu.callback,
      -					event: e,
      -					parentMenu: that,
      -					ignore_item_callbacks: value.submenu.ignore_item_callbacks,
      -					title: value.submenu.title,
      -					autoopen: options.autoopen
      -				});
      -				close_parent = false;
      -			}
      -		}
      -
      -		if(close_parent && !that.lock)
      -			that.close();
      -	}
      -
      -	return element;
      -}
      -
      -ContextMenu.prototype.close = function(e, ignore_parent_menu)
      -{
      -	if(this.root.parentNode)
      -		this.root.parentNode.removeChild( this.root );
      -	if(this.parentMenu && !ignore_parent_menu)
      -	{
      -		this.parentMenu.lock = false;
      -		this.parentMenu.current_submenu = null;
      -		if( e === undefined )
      -			this.parentMenu.close();
      -		else if( e && !ContextMenu.isCursorOverElement( e, this.parentMenu.root) )
      -		{
      -			ContextMenu.trigger( this.parentMenu.root, "mouseleave", e );
      -		}
      -	}
      -	if(this.current_submenu)
      -		this.current_submenu.close(e, true);
      -}
      -
      -//this code is used to trigger events easily (used in the context menu mouseleave
      -ContextMenu.trigger = function(element, event_name, params, origin)
      -{
      -	var evt = document.createEvent( 'CustomEvent' );
      -	evt.initCustomEvent( event_name, true,true, params ); //canBubble, cancelable, detail
      -	evt.srcElement = origin;
      -	if( element.dispatchEvent )
      -		element.dispatchEvent( evt );
      -	else if( element.__events )
      -		element.__events.dispatchEvent( evt );
      -	//else nothing seems binded here so nothing to do
      -	return evt;
      -}
      -
      -//returns the top most menu
      -ContextMenu.prototype.getTopMenu = function()
      -{
      -	if( this.options.parentMenu )
      -		return this.options.parentMenu.getTopMenu();
      -	return this;
      -}
      -
      -ContextMenu.prototype.getFirstEvent = function()
      -{
      -	if( this.options.parentMenu )
      -		return this.options.parentMenu.getFirstEvent();
      -	return this.options.event;
      -}
      -
      -
      -
      -ContextMenu.isCursorOverElement = function( event, element )
      -{
      -	var left = event.pageX;
      -	var top = event.pageY;
      -	var rect = element.getBoundingClientRect();
      -	if(!rect)
      -		return false;
      -	if(top > rect.top && top < (rect.top + rect.height) &&
      -		left > rect.left && left < (rect.left + rect.width) )
      -		return true;
      -	return false;
      -}
      -
      -
      -
      -LiteGraph.ContextMenu = ContextMenu;
      -
      -LiteGraph.closeAllContextMenus = function( ref_window )
      -{
      -	ref_window = ref_window || window;
      -
      -	var elements = ref_window.document.querySelectorAll(".litecontextmenu");
      -	if(!elements.length)
      -		return;
      -
      -	var result = [];
      -	for(var i = 0; i < elements.length; i++)
      -		result.push(elements[i]);
      -
      -	for(var i in result)
      -	{
      -		if(result[i].close)
      -			result[i].close();
      -		else if(result[i].parentNode)
      -			result[i].parentNode.removeChild( result[i] );
      -	}
      -}
      -
      -LiteGraph.extendClass = function ( target, origin )
      -{
      -	for(var i in origin) //copy class properties
      -	{
      -		if(target.hasOwnProperty(i))
      -			continue;
      -		target[i] = origin[i];
      -	}
      -
      -	if(origin.prototype) //copy prototype properties
      -		for(var i in origin.prototype) //only enumerables
      -		{
      -			if(!origin.prototype.hasOwnProperty(i))
      -				continue;
      -
      -			if(target.prototype.hasOwnProperty(i)) //avoid overwritting existing ones
      -				continue;
      -
      -			//copy getters
      -			if(origin.prototype.__lookupGetter__(i))
      -				target.prototype.__defineGetter__(i, origin.prototype.__lookupGetter__(i));
      -			else
      -				target.prototype[i] = origin.prototype[i];
      -
      -			//and setters
      -			if(origin.prototype.__lookupSetter__(i))
      -				target.prototype.__defineSetter__(i, origin.prototype.__lookupSetter__(i));
      -		}
      -}
      -
      -//used to create nodes from wrapping functions
      -LiteGraph.getParameterNames = function(func) {
      -    return (func + '')
      -      .replace(/[/][/].*$/mg,'') // strip single-line comments
      -      .replace(/\s+/g, '') // strip white space
      -      .replace(/[/][*][^/*]*[*][/]/g, '') // strip multi-line comments  /**/
      -      .split('){', 1)[0].replace(/^[^(]*[(]/, '') // extract the parameters
      -      .replace(/=[^,]+/g, '') // strip any ES6 defaults
      -      .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 ||
      -		  window.mozRequestAnimationFrame    ||
      -		  (function( callback ){
      -			window.setTimeout(callback, 1000 / 60);
      -		  });
      -}
      -
      -})(this);
      -
      -if(typeof(exports) != "undefined")
      -	exports.LiteGraph = this.LiteGraph;
      +(function(global){
      +// *************************************************************
      +//   LiteGraph CLASS                                     *******
      +// *************************************************************
      +
      +/* FYI: links are stored in graph.links with this structure per object
      +{
      +	id: number
      +	type: string,
      +	origin_id: number,
      +	origin_slot: number,
      +	target_id: number,
      +	target_slot: number,
      +	data: *
      +};
      +*/
      +
      +/**
      +* The Global Scope. It contains all the registered node classes.
      +*
      +* @class LiteGraph
      +* @constructor
      +*/
      +
      +var LiteGraph = global.LiteGraph = {
      +
      +	NODE_TITLE_HEIGHT: 20,
      +	NODE_SLOT_HEIGHT: 15,
      +	NODE_WIDGET_HEIGHT: 20,
      +	NODE_WIDTH: 140,
      +	NODE_MIN_WIDTH: 50,
      +	NODE_COLLAPSED_RADIUS: 10,
      +	NODE_COLLAPSED_WIDTH: 80,
      +	CANVAS_GRID_SIZE: 10,
      +	NODE_TITLE_COLOR: "#999",
      +	NODE_TEXT_SIZE: 14,
      +	NODE_TEXT_COLOR: "#AAA",
      +	NODE_SUBTEXT_SIZE: 12,
      +	NODE_DEFAULT_COLOR: "#333",
      +	NODE_DEFAULT_BGCOLOR: "#444",
      +	NODE_DEFAULT_BOXCOLOR: "#888",
      +	NODE_DEFAULT_SHAPE: "box",
      +	MAX_NUMBER_OF_NODES: 1000, //avoid infinite loops
      +	DEFAULT_POSITION: [100,100],//default node position
      +	node_images_path: "",
      +
      +	VALID_SHAPES: ["default","box","round","card"], //,"circle"
      +
      +	//shapes are used for nodes but also for slots
      +	BOX_SHAPE: 1,
      +	ROUND_SHAPE: 2,
      +	CIRCLE_SHAPE: 3,
      +	CARD_SHAPE: 4,
      +	ARROW_SHAPE: 5,
      +
      +	//enums
      +	INPUT: 1,
      +	OUTPUT: 2,
      +
      +	EVENT: -1, //for outputs
      +	ACTION: -1, //for inputs
      +
      +	ALWAYS: 0,
      +	ON_EVENT: 1,
      +	NEVER: 2,
      +	ON_TRIGGER: 3,
      +
      +	NORMAL_TITLE: 0,
      +	NO_TITLE: 1,
      +	TRANSPARENT_TITLE: 2,
      +	AUTOHIDE_TITLE: 3,
      +
      +	proxy: null, //used to redirect calls
      +
      +	debug: false,
      +	throw_errors: true,
      +	allow_scripts: true,
      +	registered_node_types: {}, //nodetypes by string
      +	node_types_by_file_extension: {}, //used for droping files in the canvas
      +	Nodes: {}, //node types by classname
      +
      +	/**
      +	* Register a node class so it can be listed when the user wants to create a new one
      +	* @method registerNodeType
      +	* @param {String} type name of the node and path
      +	* @param {Class} base_class class containing the structure of a node
      +	*/
      +
      +	registerNodeType: function(type, base_class)
      +	{
      +		if(!base_class.prototype)
      +			throw("Cannot register a simple object, it must be a class with a prototype");
      +		base_class.type = type;
      +
      +		if(LiteGraph.debug)
      +			console.log("Node registered: " + type);
      +
      +		var categories = type.split("/");
      +		var classname = base_class.name;
      +
      +		var pos = type.lastIndexOf("/");
      +		base_class.category = type.substr(0,pos);
      +
      +		if(!base_class.title)
      +			base_class.title = classname;
      +		//info.name = name.substr(pos+1,name.length - pos);
      +
      +		//extend class
      +		if(base_class.prototype) //is a class
      +			for(var i in LGraphNode.prototype)
      +				if(!base_class.prototype[i])
      +					base_class.prototype[i] = LGraphNode.prototype[i];
      +
      +		Object.defineProperty( base_class.prototype, "shape",{
      +			set: function(v) {
      +				switch(v)
      +				{
      +					case "default": delete this._shape; break;
      +					case "box": this._shape = LiteGraph.BOX_SHAPE; break;
      +					case "round": this._shape = LiteGraph.ROUND_SHAPE; break;
      +					case "circle": this._shape = LiteGraph.CIRCLE_SHAPE; break;
      +					case "card": this._shape = LiteGraph.CARD_SHAPE; break;
      +					default:
      +						this._shape = v;
      +				}
      +			},
      +			get: function(v)
      +			{
      +				return this._shape;
      +			},
      +			enumerable: true
      +		});
      +
      +		this.registered_node_types[ type ] = base_class;
      +		if(base_class.constructor.name)
      +			this.Nodes[ classname ] = base_class;
      +
      +		//warnings
      +		if(base_class.prototype.onPropertyChange)
      +			console.warn("LiteGraph node class " + type + " has onPropertyChange method, it must be called onPropertyChanged with d at the end");
      +
      +		if( base_class.supported_extensions )
      +		{
      +			for(var i in base_class.supported_extensions )
      +				this.node_types_by_file_extension[ base_class.supported_extensions[i].toLowerCase() ] = base_class;
      +		}
      +	},
      +
      +	/**
      +	* Create a new node type by passing a function, it wraps it with a propper class and generates inputs according to the parameters of the function.
      +	* Useful to wrap simple methods that do not require properties, and that only process some input to generate an output.
      +	* @method wrapFunctionAsNode
      +	* @param {String} name node name with namespace (p.e.: 'math/sum')
      +	* @param {Function} func
      +	* @param {Array} param_types [optional] an array containing the type of every parameter, otherwise parameters will accept any type
      +	* @param {String} return_type [optional] string with the return type, otherwise it will be generic
      +	*/
      +	wrapFunctionAsNode: function( name, func, param_types, return_type )
      +	{
      +		var params = Array(func.length);
      +		var code = "";
      +		var names = LiteGraph.getParameterNames( func );
      +		for(var i = 0; i < names.length; ++i)
      +			code += "this.addInput('"+names[i]+"',"+(param_types && param_types[i] ? "'" + param_types[i] + "'" : "0") + ");\n";
      +		code += "this.addOutput('out',"+( return_type ? "'" + return_type + "'" : 0 )+");\n";
      +		var classobj = Function(code);
      +		classobj.title = name.split("/").pop();
      +		classobj.desc = "Generated from " + func.name;
      +		classobj.prototype.onExecute = function onExecute()
      +		{
      +			for(var i = 0; i < params.length; ++i)
      +				params[i] = this.getInputData(i);
      +			var r = func.apply( this, params );
      +			this.setOutputData(0,r);
      +		}
      +		this.registerNodeType( name, classobj );
      +	},
      +
      +	/**
      +	* Adds this method to all nodetypes, existing and to be created
      +	* (You can add it to LGraphNode.prototype but then existing node types wont have it)
      +	* @method addNodeMethod
      +	* @param {Function} func
      +	*/
      +	addNodeMethod: function( name, func )
      +	{
      +		LGraphNode.prototype[name] = func;
      +		for(var i in this.registered_node_types)
      +		{
      +			var type = this.registered_node_types[i];
      +			if(type.prototype[name])
      +				type.prototype["_" + name] = type.prototype[name]; //keep old in case of replacing
      +			type.prototype[name] = func;
      +		}
      +	},
      +
      +	/**
      +	* Create a node of a given type with a name. The node is not attached to any graph yet.
      +	* @method createNode
      +	* @param {String} type full name of the node class. p.e. "math/sin"
      +	* @param {String} name a name to distinguish from other nodes
      +	* @param {Object} options to set options
      +	*/
      +
      +	createNode: function( type, title, options )
      +	{
      +		var base_class = this.registered_node_types[type];
      +		if (!base_class)
      +		{
      +			if(LiteGraph.debug)
      +				console.log("GraphNode type \"" + type + "\" not registered.");
      +			return null;
      +		}
      +
      +		var prototype = base_class.prototype || base_class;
      +
      +		title = title || base_class.title || type;
      +
      +		var node = new base_class( title );
      +		node.type = type;
      +
      +		if(!node.title && title) node.title = title;
      +		if(!node.properties) node.properties = {};
      +		if(!node.properties_info) node.properties_info = [];
      +		if(!node.flags) node.flags = {};
      +		if(!node.size) node.size = node.computeSize();
      +		if(!node.pos) node.pos = LiteGraph.DEFAULT_POSITION.concat();
      +		if(!node.mode) node.mode = LiteGraph.ALWAYS;
      +
      +		//extra options
      +		if(options)
      +		{
      +			for(var i in options)
      +				node[i] = options[i];
      +		}
      +
      +		return node;
      +	},
      +
      +	/**
      +	* Returns a registered node type with a given name
      +	* @method getNodeType
      +	* @param {String} type full name of the node class. p.e. "math/sin"
      +	* @return {Class} the node class
      +	*/
      +
      +	getNodeType: function(type)
      +	{
      +		return this.registered_node_types[type];
      +	},
      +
      +
      +	/**
      +	* Returns a list of node types matching one category
      +	* @method getNodeType
      +	* @param {String} category category name
      +	* @return {Array} array with all the node classes
      +	*/
      +
      +	getNodeTypesInCategory: function( category, filter )
      +	{
      +		var r = [];
      +		for(var i in this.registered_node_types)
      +		{
      +			var type = this.registered_node_types[i];
      +			if(filter && type.filter && type.filter != filter)
      +				continue;
      +
      +			if(category == "" )
      +			{
      +				if (type.category == null)
      +					r.push(type);
      +			}
      +			else if (type.category == category)
      +				r.push(type);
      +		}
      +
      +		return r;
      +	},
      +
      +	/**
      +	* Returns a list with all the node type categories
      +	* @method getNodeTypesCategories
      +	* @return {Array} array with all the names of the categories
      +	*/
      +
      +	getNodeTypesCategories: function()
      +	{
      +		var categories = {"":1};
      +		for(var i in this.registered_node_types)
      +			if(this.registered_node_types[i].category && !this.registered_node_types[i].skip_list)
      +				categories[ this.registered_node_types[i].category ] = 1;
      +		var result = [];
      +		for(var i in categories)
      +			result.push(i);
      +		return result;
      +	},
      +
      +	//debug purposes: reloads all the js scripts that matches a wilcard
      +	reloadNodes: function (folder_wildcard)
      +	{
      +		var tmp = document.getElementsByTagName("script");
      +		//weird, this array changes by its own, so we use a copy
      +		var script_files = [];
      +		for(var i in tmp)
      +			script_files.push(tmp[i]);
      +
      +
      +		var docHeadObj = document.getElementsByTagName("head")[0];
      +		folder_wildcard = document.location.href + folder_wildcard;
      +
      +		for(var i in script_files)
      +		{
      +			var src = script_files[i].src;
      +			if( !src || src.substr(0,folder_wildcard.length ) != folder_wildcard)
      +				continue;
      +
      +			try
      +			{
      +				if(LiteGraph.debug)
      +					console.log("Reloading: " + src);
      +				var dynamicScript = document.createElement("script");
      +				dynamicScript.type = "text/javascript";
      +				dynamicScript.src = src;
      +				docHeadObj.appendChild(dynamicScript);
      +				docHeadObj.removeChild(script_files[i]);
      +			}
      +			catch (err)
      +			{
      +				if(LiteGraph.throw_errors)
      +					throw err;
      +				if(LiteGraph.debug)
      +					console.log("Error while reloading " + src);
      +			}
      +		}
      +
      +		if(LiteGraph.debug)
      +			console.log("Nodes reloaded");
      +	},
      +
      +	//separated just to improve if it doesnt work
      +	cloneObject: function(obj, target)
      +	{
      +		if(obj == null) return null;
      +		var r = JSON.parse( JSON.stringify( obj ) );
      +		if(!target) return r;
      +
      +		for(var i in r)
      +			target[i] = r[i];
      +		return target;
      +	},
      +
      +	isValidConnection: function( type_a, type_b )
      +	{
      +		if( !type_a ||  //generic output
      +			!type_b || //generic input
      +			type_a == type_b || //same type (is valid for triggers)
      +			type_a == LiteGraph.EVENT && type_b == LiteGraph.ACTION )
      +        return true;
      +
      +		// Enforce string type to handle toLowerCase call (-1 number not ok)
      +		type_a = String(type_a); 
      +		type_b = String(type_b);
      +		type_a = type_a.toLowerCase();
      +		type_b = type_b.toLowerCase();
      +
      +		// For nodes supporting multiple connection types
      +		if( type_a.indexOf(",") == -1 && type_b.indexOf(",") == -1 )
      +			return type_a == type_b;
      +
      +		// Check all permutations to see if one is valid
      +		var supported_types_a = type_a.split(",");
      +		var supported_types_b = type_b.split(",");
      +		for(var i = 0; i < supported_types_a.length; ++i)
      +			for(var j = 0; j < supported_types_b.length; ++j)
      +				if( supported_types_a[i] == supported_types_b[j] )
      +					return true;
      +
      +		return false;
      +	}
      +};
      +
      +//timer that works everywhere
      +if(typeof(performance) != "undefined")
      +	LiteGraph.getTime = performance.now.bind(performance);
      +else if(typeof(Date) != "undefined" && Date.now)
      +	LiteGraph.getTime = Date.now.bind(Date);
      +else if(typeof(process) != "undefined")
      +	LiteGraph.getTime = function(){
      +		var t = process.hrtime();
      +		return t[0]*0.001 + t[1]*(1e-6);
      +	}
      +else
      +  LiteGraph.getTime = function getTime() { return (new Date).getTime(); }
      +
      +
      +
      +
      +
      +
      +//*********************************************************************************
      +// LGraph CLASS
      +//*********************************************************************************
      +
      +/**
      +* 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.
      +*
      +* @class LGraph
      +* @constructor
      +* @param {Object} o data from previous serialization [optional]
      +*/
      +
      +function LGraph( o )
      +{
      +	if (LiteGraph.debug)
      +		console.log("Graph created");
      +	this.list_of_graphcanvas = null;
      +	this.clear();
      +
      +	if(o)
      +		this.configure(o);
      +}
      +
      +global.LGraph = LiteGraph.LGraph = LGraph;
      +
      +//default supported types
      +LGraph.supported_types = ["number","string","boolean"];
      +
      +//used to know which types of connections support this graph (some graphs do not allow certain types)
      +LGraph.prototype.getSupportedTypes = function() { return this.supported_types || LGraph.supported_types; }
      +
      +LGraph.STATUS_STOPPED = 1;
      +LGraph.STATUS_RUNNING = 2;
      +
      +/**
      +* Removes all nodes from this graph
      +* @method clear
      +*/
      +
      +LGraph.prototype.clear = function()
      +{
      +	this.stop();
      +	this.status = LGraph.STATUS_STOPPED;
      +
      +	this.last_node_id = 1;
      +	this.last_link_id = 1;
      +
      +	this._version = -1; //used to detect changes
      +
      +	//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
      +
      +	//other scene stuff
      +	this._groups = [];
      +
      +	//links
      +	this.links = {}; //container with all the links
      +
      +	//iterations
      +	this.iteration = 0;
      +
      +	//custom data
      +	this.config = {};
      +
      +	//timing
      +	this.globaltime = 0;
      +	this.runningtime = 0;
      +	this.fixedtime =  0;
      +	this.fixedtime_lapse = 0.01;
      +	this.elapsed_time = 0.01;
      +	this.last_update_time = 0;
      +	this.starttime = 0;
      +
      +	this.catch_errors = true;
      +
      +	//subgraph_data
      +	this.global_inputs = {};
      +	this.global_outputs = {};
      +
      +	//notify canvas to redraw
      +	this.change();
      +
      +	this.sendActionToCanvas("clear");
      +}
      +
      +/**
      +* Attach Canvas to this graph
      +* @method attachCanvas
      +* @param {GraphCanvas} graph_canvas
      +*/
      +
      +LGraph.prototype.attachCanvas = function(graphcanvas)
      +{
      +	if(graphcanvas.constructor != LGraphCanvas)
      +		throw("attachCanvas expects a LGraphCanvas instance");
      +	if(graphcanvas.graph && graphcanvas.graph != this)
      +		graphcanvas.graph.detachCanvas( graphcanvas );
      +
      +	graphcanvas.graph = this;
      +	if(!this.list_of_graphcanvas)
      +		this.list_of_graphcanvas = [];
      +	this.list_of_graphcanvas.push(graphcanvas);
      +}
      +
      +/**
      +* Detach Canvas from this graph
      +* @method detachCanvas
      +* @param {GraphCanvas} graph_canvas
      +*/
      +LGraph.prototype.detachCanvas = function(graphcanvas)
      +{
      +	if(!this.list_of_graphcanvas)
      +		return;
      +
      +	var pos = this.list_of_graphcanvas.indexOf( graphcanvas );
      +	if(pos == -1)
      +		return;
      +	graphcanvas.graph = null;
      +	this.list_of_graphcanvas.splice(pos,1);
      +}
      +
      +/**
      +* Starts running this graph every interval milliseconds.
      +* @method start
      +* @param {number} interval amount of milliseconds between executions, default is 1
      +*/
      +
      +LGraph.prototype.start = function(interval)
      +{
      +	if( this.status == LGraph.STATUS_RUNNING )
      +		return;
      +	this.status = LGraph.STATUS_RUNNING;
      +
      +	if(this.onPlayEvent)
      +		this.onPlayEvent();
      +
      +	this.sendEventToAllNodes("onStart");
      +
      +	//launch
      +	this.starttime = LiteGraph.getTime();
      +	this.last_update_time = this.starttime;
      +	interval = interval || 1;
      +	var that = this;
      +
      +	this.execution_timer_id = setInterval( function() {
      +		//execute
      +		that.runStep(1, !this.catch_errors );
      +	},interval);
      +}
      +
      +/**
      +* Stops the execution loop of the graph
      +* @method stop execution
      +*/
      +
      +LGraph.prototype.stop = function()
      +{
      +	if( this.status == LGraph.STATUS_STOPPED )
      +		return;
      +
      +	this.status = LGraph.STATUS_STOPPED;
      +
      +	if(this.onStopEvent)
      +		this.onStopEvent();
      +
      +	if(this.execution_timer_id != null)
      +		clearInterval(this.execution_timer_id);
      +	this.execution_timer_id = null;
      +
      +	this.sendEventToAllNodes("onStop");
      +}
      +
      +/**
      +* Run N steps (cycles) of the graph
      +* @method runStep
      +* @param {number} num number of steps to run, default is 1
      +*/
      +
      +LGraph.prototype.runStep = function( num, do_not_catch_errors )
      +{
      +	num = num || 1;
      +
      +	var start = LiteGraph.getTime();
      +	this.globaltime = 0.001 * (start - this.starttime);
      +
      +	var nodes = this._nodes_executable ? this._nodes_executable : this._nodes;
      +	if(!nodes)
      +		return;
      +
      +	if( do_not_catch_errors )
      +	{
      +		//iterations
      +		for(var i = 0; i < num; i++)
      +		{
      +			for( var j = 0, l = nodes.length; j < l; ++j )
      +			{
      +				var node = nodes[j];
      +				if( node.mode == LiteGraph.ALWAYS && node.onExecute )
      +					node.onExecute();
      +			}
      +
      +			this.fixedtime += this.fixedtime_lapse;
      +			if( this.onExecuteStep )
      +				this.onExecuteStep();
      +		}
      +
      +		if( this.onAfterExecute )
      +			this.onAfterExecute();
      +	}
      +	else
      +	{
      +		try
      +		{
      +			//iterations
      +			for(var i = 0; i < num; i++)
      +			{
      +				for( var j = 0, l = nodes.length; j < l; ++j )
      +				{
      +					var node = nodes[j];
      +					if( node.mode == LiteGraph.ALWAYS && node.onExecute )
      +						node.onExecute();
      +				}
      +
      +				this.fixedtime += this.fixedtime_lapse;
      +				if( this.onExecuteStep )
      +					this.onExecuteStep();
      +			}
      +
      +			if( this.onAfterExecute )
      +				this.onAfterExecute();
      +			this.errors_in_execution = false;
      +		}
      +		catch (err)
      +		{
      +			this.errors_in_execution = true;
      +			if(LiteGraph.throw_errors)
      +				throw err;
      +			if(LiteGraph.debug)
      +				console.log("Error during execution: " + err);
      +			this.stop();
      +		}
      +	}
      +
      +	var now = LiteGraph.getTime();
      +	var elapsed = now - start;
      +	if (elapsed == 0)
      +		elapsed = 1;
      +	this.execution_time = 0.001 * elapsed;
      +	this.globaltime += 0.001 * elapsed;
      +	this.iteration += 1;
      +	this.elapsed_time = (now - this.last_update_time) * 0.001;
      +	this.last_update_time = now;
      +}
      +
      +/**
      +* Updates the graph execution order according to relevance of the nodes (nodes with only outputs have more relevance than
      +* nodes with only inputs.
      +* @method updateExecutionOrder
      +*/
      +LGraph.prototype.updateExecutionOrder = function()
      +{
      +	this._nodes_in_order = this.computeExecutionOrder( false );
      +	this._nodes_executable = [];
      +	for(var i = 0; i < this._nodes_in_order.length; ++i)
      +		if( this._nodes_in_order[i].onExecute )
      +			this._nodes_executable.push( this._nodes_in_order[i] );
      +}
      +
      +//This is more internal, it computes the order and returns it
      +LGraph.prototype.computeExecutionOrder = function( only_onExecute, set_level )
      +{
      +	var L = [];
      +	var S = [];
      +	var M = {};
      +	var visited_links = {}; //to avoid repeating links
      +	var remaining_links = {}; //to a
      +
      +	//search for the nodes without inputs (starting nodes)
      +	for (var i = 0, l = this._nodes.length; i < l; ++i)
      +	{
      +		var node = this._nodes[i];
      +		if( only_onExecute && !node.onExecute )
      +			continue;
      +
      +		M[node.id] = node; //add to pending nodes
      +
      +		var num = 0; //num of input connections
      +		if(node.inputs)
      +			for(var j = 0, l2 = node.inputs.length; j < l2; j++)
      +				if(node.inputs[j] && node.inputs[j].link != null)
      +					num += 1;
      +
      +		if(num == 0) //is a starting node
      +		{
      +			S.push(node);
      +			if(set_level)
      +				node._level = 1;
      +		}
      +		else //num of input links
      +		{
      +			if(set_level)
      +				node._level = 0;
      +			remaining_links[node.id] = num;
      +		}
      +	}
      +
      +	while(true)
      +	{
      +		if(S.length == 0)
      +			break;
      +
      +		//get an starting node
      +		var node = S.shift();
      +		L.push(node); //add to ordered list
      +		delete M[node.id]; //remove from the pending nodes
      +
      +		if(!node.outputs)
      +			continue;
      +
      +		//for every output
      +		for(var i = 0; i < node.outputs.length; i++)
      +		{
      +			var output = node.outputs[i];
      +			//not connected
      +			if(output == null || output.links == null || output.links.length == 0)
      +				continue;
      +
      +			//for every connection
      +			for(var j = 0; j < output.links.length; j++)
      +			{
      +				var link_id = output.links[j];
      +				var link = this.links[link_id];
      +				if(!link)
      +					continue;
      +
      +				//already visited link (ignore it)
      +				if(visited_links[ link.id ])
      +					continue;
      +
      +				var target_node = this.getNodeById( link.target_id );
      +				if(target_node == null)
      +				{
      +					visited_links[ link.id ] = true;
      +					continue;
      +				}
      +
      +				if(set_level && (!target_node._level || target_node._level <= node._level))
      +					target_node._level = node._level + 1;
      +
      +				visited_links[link.id] = true; //mark as visited
      +				remaining_links[target_node.id] -= 1; //reduce the number of links remaining
      +				if (remaining_links[ target_node.id ] == 0)
      +					S.push(target_node); //if no more links, then add to starters array
      +			}
      +		}
      +	}
      +
      +	//the remaining ones (loops)
      +	for(var i in M)
      +		L.push( M[i] );
      +
      +	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; ++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;
      +}
      +
      +/**
      +* Returns all the nodes that could affect this one (ancestors) by crawling all the inputs recursively.
      +* It doesnt include the node itself
      +* @method getAncestors
      +* @return {Array} an array with all the LGraphNodes that affect this node, in order of execution
      +*/
      +LGraph.prototype.getAncestors = function( node )
      +{
      +	var ancestors = [];
      +	var pending = [node];
      +	var visited = {};
      +
      +	while (pending.length)
      +	{
      +		var current = pending.shift();
      +		if(!current.inputs)
      +			continue;
      +		if( !visited[ current.id ] && current != node )
      +		{
      +			visited[ current.id ] = true;
      +			ancestors.push( current );
      +		}
      +
      +		for(var i = 0; i < current.inputs.length;++i)
      +		{
      +			var input = current.getInputNode(i);
      +			if( input && ancestors.indexOf( input ) == -1)
      +			{
      +				pending.push( input );
      +			}
      +		}
      +	}
      +
      +	ancestors.sort(function(a,b){ return a.order - b.order;});
      +	return ancestors;
      +}
      +
      +/**
      +* Positions every node in a more readable manner
      +* @method arrange
      +*/
      +LGraph.prototype.arrange = function( margin )
      +{
      +	margin = margin || 40;
      +
      +	var nodes = this.computeExecutionOrder( false, true );
      +	var columns = [];
      +	for(var i = 0; i < nodes.length; ++i)
      +	{
      +		var node = nodes[i];
      +		var col = node._level || 1;
      +		if(!columns[col])
      +			columns[col] = [];
      +		columns[col].push( node );
      +	}
      +
      +	var x = margin;
      +
      +	for(var i = 0; i < columns.length; ++i)
      +	{
      +		var column = columns[i];
      +		if(!column)
      +			continue;
      +		var max_size = 100;
      +		var y = margin;
      +		for(var j = 0; j < column.length; ++j)
      +		{
      +			var node = column[j];
      +			node.pos[0] = x;
      +			node.pos[1] = y;
      +			if(node.size[0] > max_size)
      +				max_size = node.size[0];
      +			y += node.size[1] + margin;
      +		}
      +		x += max_size + margin;
      +	}
      +
      +	this.setDirtyCanvas(true,true);
      +}
      +
      +
      +/**
      +* Returns the amount of time the graph has been running in milliseconds
      +* @method getTime
      +* @return {number} number of milliseconds the graph has been running
      +*/
      +LGraph.prototype.getTime = function()
      +{
      +	return this.globaltime;
      +}
      +
      +/**
      +* Returns the amount of time accumulated using the fixedtime_lapse var. This is used in context where the time increments should be constant
      +* @method getFixedTime
      +* @return {number} number of milliseconds the graph has been running
      +*/
      +
      +LGraph.prototype.getFixedTime = function()
      +{
      +	return this.fixedtime;
      +}
      +
      +/**
      +* Returns the amount of time it took to compute the latest iteration. Take into account that this number could be not correct
      +* if the nodes are using graphical actions
      +* @method getElapsedTime
      +* @return {number} number of milliseconds it took the last cycle
      +*/
      +
      +LGraph.prototype.getElapsedTime = function()
      +{
      +	return this.elapsed_time;
      +}
      +
      +/**
      +* Sends an event to all the nodes, useful to trigger stuff
      +* @method sendEventToAllNodes
      +* @param {String} eventname the name of the event (function to be called)
      +* @param {Array} params parameters in array format
      +*/
      +
      +LGraph.prototype.sendEventToAllNodes = function( eventname, params, mode )
      +{
      +	mode = mode || LiteGraph.ALWAYS;
      +
      +	var nodes = this._nodes_in_order ? this._nodes_in_order : this._nodes;
      +	if(!nodes)
      +		return;
      +
      +	for( var j = 0, l = nodes.length; j < l; ++j )
      +	{
      +		var node = nodes[j];
      +		if(node[eventname] && node.mode == mode )
      +		{
      +			if(params === undefined)
      +				node[eventname]();
      +			else if(params && params.constructor === Array)
      +				node[eventname].apply( node, params );
      +			else
      +				node[eventname](params);
      +		}
      +	}
      +}
      +
      +LGraph.prototype.sendActionToCanvas = function(action, params)
      +{
      +	if(!this.list_of_graphcanvas)
      +		return;
      +
      +	for(var i = 0; i < this.list_of_graphcanvas.length; ++i)
      +	{
      +		var c = this.list_of_graphcanvas[i];
      +		if( c[action] )
      +			c[action].apply(c, params);
      +	}
      +}
      +
      +/**
      +* Adds a new node instasnce to this graph
      +* @method add
      +* @param {LGraphNode} node the instance of the node
      +*/
      +
      +LGraph.prototype.add = function( node, skip_compute_order)
      +{
      +	if(!node)
      +		return;
      +
      +	//groups
      +	if( node.constructor === LGraphGroup )
      +	{
      +		this._groups.push( node );
      +		this.setDirtyCanvas(true);
      +		this.change();
      +		node.graph = this;
      +		this._version++;
      +		return;
      +	}
      +
      +	//nodes
      +	if(node.id != -1 && this._nodes_by_id[node.id] != null)
      +	{
      +		console.warn("LiteGraph: there is already a node with this ID, changing it");
      +		node.id = ++this.last_node_id;
      +	}
      +
      +	if(this._nodes.length >= LiteGraph.MAX_NUMBER_OF_NODES)
      +		throw("LiteGraph: max number of nodes in a graph reached");
      +
      +	//give him an id
      +	if(node.id == null || node.id == -1)
      +		node.id = ++this.last_node_id;
      +	else if (this.last_node_id < node.id)
      +		this.last_node_id = node.id;
      +
      +
      +	node.graph = this;
      +	this._version++;
      +
      +	this._nodes.push(node);
      +	this._nodes_by_id[node.id] = node;
      +
      +	if(node.onAdded)
      +		node.onAdded( this );
      +
      +	if(this.config.align_to_grid)
      +		node.alignToGrid();
      +
      +	if(!skip_compute_order)
      +		this.updateExecutionOrder();
      +
      +	if(this.onNodeAdded)
      +		this.onNodeAdded(node);
      +
      +
      +	this.setDirtyCanvas(true);
      +	this.change();
      +
      +	return node; //to chain actions
      +}
      +
      +/**
      +* Removes a node from the graph
      +* @method remove
      +* @param {LGraphNode} node the instance of the node
      +*/
      +
      +LGraph.prototype.remove = function(node)
      +{
      +	if(node.constructor === LiteGraph.LGraphGroup)
      +	{
      +		var index = this._groups.indexOf(node);
      +		if(index != -1)
      +			this._groups.splice(index,1);
      +		node.graph = null;
      +		this._version++;
      +		this.setDirtyCanvas(true,true);
      +		this.change();
      +		return;
      +	}
      +
      +	if(this._nodes_by_id[node.id] == null)
      +		return; //not found
      +
      +	if(node.ignore_remove)
      +		return; //cannot be removed
      +
      +	//disconnect inputs
      +	if(node.inputs)
      +		for(var i = 0; i < node.inputs.length; i++)
      +		{
      +			var slot = node.inputs[i];
      +			if(slot.link != null)
      +				node.disconnectInput(i);
      +		}
      +
      +	//disconnect outputs
      +	if(node.outputs)
      +		for(var i = 0; i < node.outputs.length; i++)
      +		{
      +			var slot = node.outputs[i];
      +			if(slot.links != null && slot.links.length)
      +				node.disconnectOutput(i);
      +		}
      +
      +	//node.id = -1; //why?
      +
      +	//callback
      +	if(node.onRemoved)
      +		node.onRemoved();
      +
      +	node.graph = null;
      +	this._version++;
      +
      +	//remove from canvas render
      +	if(this.list_of_graphcanvas)
      +	{
      +		for(var i = 0; i < this.list_of_graphcanvas.length; ++i)
      +		{
      +			var canvas = this.list_of_graphcanvas[i];
      +			if(canvas.selected_nodes[node.id])
      +				delete canvas.selected_nodes[node.id];
      +			if(canvas.node_dragged == node)
      +				canvas.node_dragged = null;
      +		}
      +	}
      +
      +	//remove from containers
      +	var pos = this._nodes.indexOf(node);
      +	if(pos != -1)
      +		this._nodes.splice(pos,1);
      +	delete this._nodes_by_id[node.id];
      +
      +	if(this.onNodeRemoved)
      +		this.onNodeRemoved(node);
      +
      +	this.setDirtyCanvas(true,true);
      +	this.change();
      +
      +	this.updateExecutionOrder();
      +}
      +
      +/**
      +* Returns a node by its id.
      +* @method getNodeById
      +* @param {Number} id
      +*/
      +
      +LGraph.prototype.getNodeById = function( id )
      +{
      +	if( id == null )
      +		return null;
      +	return this._nodes_by_id[ id ];
      +}
      +
      +/**
      +* Returns a list of nodes that matches a class
      +* @method findNodesByClass
      +* @param {Class} classObject the class itself (not an string)
      +* @return {Array} a list with all the nodes of this type
      +*/
      +
      +LGraph.prototype.findNodesByClass = function(classObject)
      +{
      +	var r = [];
      +	for(var i = 0, l = this._nodes.length; i < l; ++i)
      +		if(this._nodes[i].constructor === classObject)
      +			r.push(this._nodes[i]);
      +	return r;
      +}
      +
      +/**
      +* Returns a list of nodes that matches a type
      +* @method findNodesByType
      +* @param {String} type the name of the node type
      +* @return {Array} a list with all the nodes of this type
      +*/
      +
      +LGraph.prototype.findNodesByType = function(type)
      +{
      +	var type = type.toLowerCase();
      +	var r = [];
      +	for(var i = 0, l = this._nodes.length; i < l; ++i)
      +		if(this._nodes[i].type.toLowerCase() == type )
      +			r.push(this._nodes[i]);
      +	return r;
      +}
      +
      +/**
      +* Returns a list of nodes that matches a name
      +* @method findNodesByTitle
      +* @param {String} name the name of the node to search
      +* @return {Array} a list with all the nodes with this name
      +*/
      +
      +LGraph.prototype.findNodesByTitle = function(title)
      +{
      +	var result = [];
      +	for(var i = 0, l = this._nodes.length; i < l; ++i)
      +		if(this._nodes[i].title == title)
      +			result.push(this._nodes[i]);
      +	return result;
      +}
      +
      +/**
      +* Returns the top-most node in this position of the canvas
      +* @method getNodeOnPos
      +* @param {number} x the x coordinate in canvas space
      +* @param {number} y the y coordinate in canvas space
      +* @param {Array} nodes_list a list with all the nodes to search from, by default is all the nodes in the graph
      +* @return {LGraphNode} the node at this position or null
      +*/
      +LGraph.prototype.getNodeOnPos = function(x,y, nodes_list)
      +{
      +	nodes_list = nodes_list || this._nodes;
      +	for (var i = nodes_list.length - 1; i >= 0; i--)
      +	{
      +		var n = nodes_list[i];
      +		if(n.isPointInside( x, y, 2 ))
      +			return n;
      +	}
      +	return null;
      +}
      +
      +/**
      +* Returns the top-most group in that position
      +* @method getGroupOnPos
      +* @param {number} x the x coordinate in canvas space
      +* @param {number} y the y coordinate in canvas space
      +* @return {LGraphGroup} the group or null
      +*/
      +LGraph.prototype.getGroupOnPos = function(x,y)
      +{
      +	for (var i = this._groups.length - 1; i >= 0; i--)
      +	{
      +		var g = this._groups[i];
      +		if(g.isPointInside( x, y, 2 ))
      +			return g;
      +	}
      +	return null;
      +}
      +
      +// ********** GLOBALS *****************
      +
      +/**
      +* 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 };
      +	this._version++;
      +
      +	if(this.onGlobalInputAdded)
      +		this.onGlobalInputAdded(name, type);
      +
      +	if(this.onGlobalsChange)
      +		this.onGlobalsChange();
      +}
      +
      +/**
      +* 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];
      +	if (!input)
      +		return;
      +	input.value = data;
      +}
      +
      +/**
      +* 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];
      +	if (!input)
      +		return null;
      +	return input.value;
      +}
      +
      +/**
      +* 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)
      +		return;
      +
      +	if(!this.global_inputs[old_name])
      +		return false;
      +
      +	if(this.global_inputs[name])
      +	{
      +		console.error("there is already one input with that name");
      +		return false;
      +	}
      +
      +	this.global_inputs[name] = this.global_inputs[old_name];
      +	delete this.global_inputs[old_name];
      +	this._version++;
      +
      +	if(this.onGlobalInputRenamed)
      +		this.onGlobalInputRenamed(old_name, name);
      +
      +	if(this.onGlobalsChange)
      +		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])
      +		return false;
      +
      +	if(this.global_inputs[name].type && this.global_inputs[name].type.toLowerCase() == type.toLowerCase() )
      +		return;
      +
      +	this.global_inputs[name].type = type;
      +	this._version++;
      +	if(this.onGlobalInputTypeChanged)
      +		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])
      +		return false;
      +
      +	delete this.global_inputs[name];
      +	this._version++;
      +
      +	if(this.onGlobalInputRemoved)
      +		this.onGlobalInputRemoved(name);
      +
      +	if(this.onGlobalsChange)
      +		this.onGlobalsChange();
      +	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 };
      +	this._version++;
      +
      +	if(this.onGlobalOutputAdded)
      +		this.onGlobalOutputAdded(name, type);
      +
      +	if(this.onGlobalsChange)
      +		this.onGlobalsChange();
      +}
      +
      +/**
      +* 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 ];
      +	if (!output)
      +		return;
      +	output.value = value;
      +}
      +
      +/**
      +* 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];
      +	if (!output)
      +		return null;
      +	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;
      +
      +
      +/**
      +* 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])
      +		return false;
      +
      +	if(this.global_outputs[name])
      +	{
      +		console.error("there is already one output with that name");
      +		return false;
      +	}
      +
      +	this.global_outputs[name] = this.global_outputs[old_name];
      +	delete this.global_outputs[old_name];
      +	this._version++;
      +
      +	if(this.onGlobalOutputRenamed)
      +		this.onGlobalOutputRenamed(old_name, name);
      +
      +	if(this.onGlobalsChange)
      +		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])
      +		return false;
      +
      +	if(this.global_outputs[name].type && this.global_outputs[name].type.toLowerCase() == type.toLowerCase() )
      +		return;
      +
      +	this.global_outputs[name].type = type;
      +	this._version++;
      +	if(this.onGlobalOutputTypeChanged)
      +		this.onGlobalOutputTypeChanged(name, type);
      +}
      +
      +/**
      +* Removes a global graph output
      +* @method removeGlobalOutput
      +* @param {String} name
      +*/
      +LGraph.prototype.removeGlobalOutput = function(name)
      +{
      +	if(!this.global_outputs[name])
      +		return false;
      +	delete this.global_outputs[name];
      +	this._version++;
      +
      +	if(this.onGlobalOutputRemoved)
      +		this.onGlobalOutputRemoved(name);
      +
      +	if(this.onGlobalsChange)
      +		this.onGlobalsChange();
      +	return true;
      +}
      +
      +LGraph.prototype.triggerInput = function(name,value)
      +{
      +	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.findNodesByTitle(name);
      +	for(var i = 0; i < nodes.length; ++i)
      +		nodes[i].setTrigger(func);
      +}
      +
      +
      +LGraph.prototype.connectionChange = function( node )
      +{
      +	this.updateExecutionOrder();
      +	if( this.onConnectionChange )
      +		this.onConnectionChange( node );
      +	this._version++;
      +	this.sendActionToCanvas("onConnectionChange");
      +}
      +
      +/**
      +* returns if the graph is in live mode
      +* @method isLive
      +*/
      +
      +LGraph.prototype.isLive = function()
      +{
      +	if(!this.list_of_graphcanvas)
      +		return false;
      +
      +	for(var i = 0; i < this.list_of_graphcanvas.length; ++i)
      +	{
      +		var c = this.list_of_graphcanvas[i];
      +		if(c.live_mode)
      +			return true;
      +	}
      +	return false;
      +}
      +
      +/* Called when something visually changed (not the graph!) */
      +LGraph.prototype.change = function()
      +{
      +	if(LiteGraph.debug)
      +		console.log("Graph changed");
      +	this.sendActionToCanvas("setDirty",[true,true]);
      +	if(this.on_change)
      +		this.on_change(this);
      +}
      +
      +LGraph.prototype.setDirtyCanvas = function(fg,bg)
      +{
      +	this.sendActionToCanvas("setDirty",[fg,bg]);
      +}
      +
      +//save and recover app state ***************************************
      +/**
      +* Creates a Object containing all the info about this graph, it can be serialized
      +* @method serialize
      +* @return {Object} value of the node
      +*/
      +LGraph.prototype.serialize = function()
      +{
      +	var nodes_info = [];
      +	for(var i = 0, l = this._nodes.length; i < l; ++i)
      +		nodes_info.push( this._nodes[i].serialize() );
      +
      +	//pack link info into a non-verbose format
      +	var links = [];
      +	for(var i in this.links) //links is an OBJECT
      +	{
      +		var link = this.links[i];
      +		links.push([ link.id, link.origin_id, link.origin_slot, link.target_id, link.target_slot, link.type ]);
      +	}
      +
      +	var groups_info = [];
      +	for(var i = 0; i < this._groups.length; ++i)
      +		groups_info.push( this._groups[i].serialize() );
      +
      +	var data = {
      +		last_node_id: this.last_node_id,
      +		last_link_id: this.last_link_id,
      +		nodes: nodes_info,
      +		links: links, 
      +		groups: groups_info,
      +		config: this.config
      +	};
      +
      +	return data;
      +}
      +
      +
      +/**
      +* Configure a graph from a JSON string
      +* @method configure
      +* @param {String} str configure a graph from a JSON string
      +* @param {Boolean} returns if there was any error parsing
      +*/
      +LGraph.prototype.configure = function( data, keep_old )
      +{
      +	if(!data)
      +		return;
      +
      +	if(!keep_old)
      +		this.clear();
      +
      +	var nodes = data.nodes;
      +
      +	//decode links info (they are very verbose)
      +	if(data.links && data.links.constructor === Array)
      +	{
      +		var links = [];
      +		for(var i = 0; i < data.links.length; ++i)
      +		{
      +			var link = data.links[i];
      +			links[ link[0] ] = { id: link[0], origin_id: link[1], origin_slot: link[2], target_id: link[3], target_slot: link[4], type: link[5] };
      +		}
      +		data.links = links;
      +	}
      +
      +	//copy all stored fields
      +	for (var i in data)
      +		this[i] = data[i];
      +
      +	var error = false;
      +
      +	//create nodes
      +	this._nodes = [];
      +	if(nodes)
      +	{
      +		for(var i = 0, l = nodes.length; i < l; ++i)
      +		{
      +			var n_info = nodes[i]; //stored info
      +			var node = LiteGraph.createNode( n_info.type, n_info.title );
      +			if(!node)
      +			{
      +				if(LiteGraph.debug)
      +					console.log("Node not found: " + n_info.type);
      +				error = true;
      +				continue;
      +			}
      +
      +			node.id = n_info.id; //id it or it will create a new id
      +			this.add(node, true); //add before configure, otherwise configure cannot create links
      +		}
      +
      +		//configure nodes afterwards so they can reach each other
      +		for(var i = 0, l = nodes.length; i < l; ++i)
      +		{
      +			var n_info = nodes[i];
      +			var node = this.getNodeById( n_info.id );
      +			if(node)
      +				node.configure( n_info );
      +		}
      +	}
      +
      +	//groups
      +	this._groups.length = 0;
      +	if( data.groups )
      +	for(var i = 0; i < data.groups.length; ++i )
      +	{
      +		var group = new LiteGraph.LGraphGroup();
      +		group.configure( data.groups[i] );
      +		this.add( group );
      +	}
      +
      +	this.updateExecutionOrder();
      +	this._version++;
      +	this.setDirtyCanvas(true,true);
      +	return error;
      +}
      +
      +LGraph.prototype.load = function(url)
      +{
      +	var that = this;
      +	var req = new XMLHttpRequest();
      +	req.open('GET', url, true);
      +	req.send(null);
      +	req.onload = function (oEvent) {
      +		if(req.status !== 200)
      +		{
      +			console.error("Error loading graph:",req.status,req.response);
      +			return;
      +		}
      +		var data = JSON.parse( req.response );
      +		that.configure(data);
      +	}
      +	req.onerror = function(err)
      +	{
      +		console.error("Error loading graph:",err);
      +	}
      +}
      +
      +LGraph.prototype.onNodeTrace = function(node, msg, color)
      +{
      +	//TODO
      +}
      +
      +// *************************************************************
      +//   Node CLASS                                          *******
      +// *************************************************************
      +
      +/*
      +	title: string
      +	pos: [x,y]
      +	size: [x,y]
      +
      +	input|output: every connection
      +		+  { name:string, type:string, pos: [x,y]=Optional, direction: "input"|"output", links: Array });
      +
      +	flags:
      +		+ clip_area: if you render outside the node, it will be cliped
      +		+ unsafe_execution: not allowed for safe execution
      +		+ skip_repeated_outputs: when adding new outputs, it wont show if there is one already connected
      +		+ resizable: if set to false it wont be resizable with the mouse
      +
      +	supported callbacks:
      +		+ onAdded: when added to graph
      +		+ onRemoved: when removed from graph
      +		+ onStart:	when the graph starts playing
      +		+ onStop:	when the graph stops playing
      +		+ onDrawForeground: render the inside widgets inside the node
      +		+ onDrawBackground: render the background area inside the node (only in edit mode)
      +		+ onMouseDown
      +		+ onMouseMove
      +		+ onMouseUp
      +		+ onMouseEnter
      +		+ onMouseLeave
      +		+ onExecute: execute the node
      +		+ onPropertyChanged: when a property is changed in the panel (return true to skip default behaviour)
      +		+ onGetInputs: returns an array of possible inputs
      +		+ onGetOutputs: returns an array of possible outputs
      +		+ onDblClick
      +		+ onSerialize
      +		+ onSelected
      +		+ onDeselected
      +		+ onDropItem : DOM item dropped over the node
      +		+ onDropFile : file dropped over the node
      +		+ onConnectInput : if returns false the incoming connection will be canceled
      +		+ onConnectionsChange : a connection changed (new one or removed) (LiteGraph.INPUT or LiteGraph.OUTPUT, slot, true if connected, link_info, input_info )
      +*/
      +
      +/**
      +* Base Class for all the node type classes
      +* @class LGraphNode
      +* @param {String} name a name for the node
      +*/
      +
      +function LGraphNode(title)
      +{
      +	this._ctor(title);
      +}
      +
      +global.LGraphNode = LiteGraph.LGraphNode = LGraphNode;
      +
      +LGraphNode.prototype._ctor = function( title )
      +{
      +	this.title = title || "Unnamed";
      +	this.size = [LiteGraph.NODE_WIDTH,60];
      +	this.graph = null;
      +
      +	this._pos = new Float32Array(10,10);
      +
      +	Object.defineProperty( this, "pos", {
      +		set: function(v)
      +		{
      +			if(!v || v.length < 2)
      +				return;
      +			this._pos[0] = v[0];
      +			this._pos[1] = v[1];
      +		},
      +		get: function()
      +		{
      +			return this._pos;
      +		},
      +		enumerable: true
      +	});
      +
      +	this.id = -1; //not know till not added
      +	this.type = null;
      +
      +	//inputs available: array of inputs
      +	this.inputs = [];
      +	this.outputs = [];
      +	this.connections = [];
      +
      +	//local data
      +	this.properties = {}; //for the values
      +	this.properties_info = []; //for the info
      +
      +	this.data = null; //persistent local data
      +	this.flags = {};
      +}
      +
      +/**
      +* configure a node from an object containing the serialized info
      +* @method configure
      +*/
      +LGraphNode.prototype.configure = function(info)
      +{
      +	if(this.graph)
      +		this.graph._version++;
      +
      +	for (var j in info)
      +	{
      +		if(j == "console")
      +			continue;
      +
      +		if(j == "properties")
      +		{
      +			//i dont want to clone properties, I want to reuse the old container
      +			for(var k in info.properties)
      +			{
      +				this.properties[k] = info.properties[k];
      +				if(this.onPropertyChanged)
      +					this.onPropertyChanged(k,info.properties[k]);
      +			}
      +			continue;
      +		}
      +
      +		if(info[j] == null)
      +			continue;
      +
      +		else if (typeof(info[j]) == 'object') //object
      +		{
      +			if(this[j] && this[j].configure)
      +				this[j].configure( info[j] );
      +			else
      +				this[j] = LiteGraph.cloneObject(info[j], this[j]);
      +		}
      +		else //value
      +			this[j] = info[j];
      +	}
      +
      +	if(!info.title)
      +		this.title = this.constructor.title;
      +
      +	if(this.onConnectionsChange)
      +	{
      +		if(this.inputs)
      +		for(var i = 0; i < this.inputs.length; ++i)
      +		{
      +			var input = this.inputs[i];
      +			var link_info = this.graph ? this.graph.links[ input.link ] : null;
      +			this.onConnectionsChange( LiteGraph.INPUT, i, true, link_info, input ); //link_info has been created now, so its updated
      +		}
      +
      +		if(this.outputs)
      +		for(var i = 0; i < this.outputs.length; ++i)
      +		{
      +			var output = this.outputs[i];
      +			if(!output.links)
      +				continue;
      +			for(var j = 0; j < output.links.length; ++j)
      +			{
      +				var link_info = this.graph ? this.graph.links[ output.links[j] ] : null;
      +				this.onConnectionsChange( LiteGraph.OUTPUT, i, true, link_info, output ); //link_info has been created now, so its updated
      +			}
      +		}
      +	}
      +
      +	//FOR LEGACY, PLEASE REMOVE ON NEXT VERSION
      +	for(var i in this.inputs)
      +	{
      +		var input = this.inputs[i];
      +		if(!input.link || !input.link.length )
      +			continue;
      +		var link = input.link;
      +		if(typeof(link) != "object")
      +			continue;
      +		input.link = link[0];
      +		if(this.graph)
      +		this.graph.links[ link[0] ] = {
      +			id: link[0],
      +			origin_id: link[1],
      +			origin_slot: link[2],
      +			target_id: link[3],
      +			target_slot: link[4]
      +		};
      +	}
      +	for(var i in this.outputs)
      +	{
      +		var output = this.outputs[i];
      +		if(!output.links || output.links.length == 0)
      +			continue;
      +		for(var j in output.links)
      +		{
      +			var link = output.links[j];
      +			if(typeof(link) != "object")
      +				continue;
      +			output.links[j] = link[0];
      +		}
      +	}
      +
      +	if( this.onConfigure )
      +		this.onConfigure( info );
      +}
      +
      +/**
      +* serialize the content
      +* @method serialize
      +*/
      +
      +LGraphNode.prototype.serialize = function()
      +{
      +	//create serialization object
      +	var o = {
      +		id: this.id,
      +		type: this.type,
      +		pos: this.pos,
      +		size: this.size,
      +		data: this.data,
      +		flags: LiteGraph.cloneObject(this.flags),
      +		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 );
      +
      +	if( !o.type )
      +		o.type = this.constructor.type;
      +
      +	if( this.color )
      +		o.color = this.color;
      +	if( this.bgcolor )
      +		o.bgcolor = this.bgcolor;
      +	if( this.boxcolor )
      +		o.boxcolor = this.boxcolor;
      +	if( this.shape )
      +		o.shape = this.shape;
      +
      +	if(this.onSerialize)
      +		this.onSerialize(o);
      +
      +	return o;
      +}
      +
      +
      +/* Creates a clone of this node */
      +LGraphNode.prototype.clone = function()
      +{
      +	var node = LiteGraph.createNode(this.type);
      +
      +	//we clone it because serialize returns shared containers
      +	var data = LiteGraph.cloneObject( this.serialize() );
      +
      +	//remove links
      +	if(data.inputs)
      +		for(var i = 0; i < data.inputs.length; ++i)
      +			data.inputs[i].link = null;
      +
      +	if(data.outputs)
      +		for(var i = 0; i < data.outputs.length; ++i)
      +		{
      +			if(data.outputs[i].links)
      +				data.outputs[i].links.length = 0;
      +		}
      +
      +	delete data["id"];
      +	//remove links
      +	node.configure(data);
      +
      +	return node;
      +}
      +
      +
      +/**
      +* serialize and stringify
      +* @method toString
      +*/
      +
      +LGraphNode.prototype.toString = function()
      +{
      +	return JSON.stringify( this.serialize() );
      +}
      +//LGraphNode.prototype.unserialize = function(info) {} //this cannot be done from within, must be done in LiteGraph
      +
      +
      +/**
      +* get the title string
      +* @method getTitle
      +*/
      +
      +LGraphNode.prototype.getTitle = function()
      +{
      +	return this.title || this.constructor.title;
      +}
      +
      +
      +
      +// Execution *************************
      +/**
      +* sets the output data
      +* @method setOutputData
      +* @param {number} slot
      +* @param {*} data
      +*/
      +LGraphNode.prototype.setOutputData = function(slot, data)
      +{
      +	if(!this.outputs)
      +		return;
      +
      +	//this maybe slow and a niche case
      +	//if(slot && slot.constructor === String)
      +	//	slot = this.findOutputSlot(slot);
      +
      +	if(slot == -1 || slot >= this.outputs.length)
      +		return;
      +
      +	var output_info = this.outputs[slot];
      +	if(!output_info)
      +		return;
      +
      +	//store data in the output itself in case we want to debug
      +	output_info._data = data;
      +
      +	//if there are connections, pass the data to the connections
      +	if( this.outputs[slot].links )
      +	{
      +		for(var i = 0; i < this.outputs[slot].links.length; i++)
      +		{
      +			var link_id = this.outputs[slot].links[i];
      +			this.graph.links[ link_id ].data = data;
      +		}
      +	}
      +}
      +
      +/**
      +* 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
      +* @return {*} data or if it is not connected returns undefined
      +*/
      +LGraphNode.prototype.getInputData = function( slot, force_update )
      +{
      +	if(!this.inputs)
      +		return; //undefined;
      +
      +	if(slot >= this.inputs.length || this.inputs[slot].link == null)
      +		return;
      +
      +	var link_id = this.inputs[slot].link;
      +	var link = this.graph.links[ link_id ];
      +	if(!link) //bug: weird case but it happens sometimes
      +		return null;
      +
      +	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;
      +
      +	if(node.updateOutputData)
      +		node.updateOutputData( link.origin_slot );
      +	else if(node.onExecute)
      +		node.onExecute();
      +
      +	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
      +* @param {number} slot
      +* @return {boolean}
      +*/
      +LGraphNode.prototype.isInputConnected = function(slot)
      +{
      +	if(!this.inputs)
      +		return false;
      +	return (slot < this.inputs.length && this.inputs[slot].link != null);
      +}
      +
      +/**
      +* tells you info about an input connection (which node, type, etc)
      +* @method getInputInfo
      +* @param {number} slot
      +* @return {Object} object or null { link: id, name: string, type: string or 0 }
      +*/
      +LGraphNode.prototype.getInputInfo = function(slot)
      +{
      +	if(!this.inputs)
      +		return null;
      +	if(slot < this.inputs.length)
      +		return this.inputs[slot];
      +	return null;
      +}
      +
      +/**
      +* returns the node connected in the input slot
      +* @method getInputNode
      +* @param {number} slot
      +* @return {LGraphNode} node or null
      +*/
      +LGraphNode.prototype.getInputNode = function( slot )
      +{
      +	if(!this.inputs)
      +		return null;
      +	if(slot >= this.inputs.length)
      +		return null;
      +	var input = this.inputs[slot];
      +	if(!input || input.link === null)
      +		return null;
      +	var link_info = this.graph.links[ input.link ];
      +	if(!link_info)
      +		return null;
      +	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
      +* @param {number} slot
      +* @return {Object}  object or null
      +*/
      +LGraphNode.prototype.getOutputData = function(slot)
      +{
      +	if(!this.outputs)
      +		return null;
      +	if(slot >= this.outputs.length)
      +		return null;
      +
      +	var info = this.outputs[slot];
      +	return info._data;
      +}
      +
      +
      +/**
      +* tells you info about an output connection (which node, type, etc)
      +* @method getOutputInfo
      +* @param {number} slot
      +* @return {Object}  object or null { name: string, type: string, links: [ ids of links in number ] }
      +*/
      +LGraphNode.prototype.getOutputInfo = function(slot)
      +{
      +	if(!this.outputs)
      +		return null;
      +	if(slot < this.outputs.length)
      +		return this.outputs[slot];
      +	return null;
      +}
      +
      +
      +/**
      +* tells you if there is a connection in one output slot
      +* @method isOutputConnected
      +* @param {number} slot
      +* @return {boolean}
      +*/
      +LGraphNode.prototype.isOutputConnected = function(slot)
      +{
      +	if(!this.outputs)
      +		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
      +* @param {number} slot
      +* @return {array}
      +*/
      +LGraphNode.prototype.getOutputNodes = function(slot)
      +{
      +	if(!this.outputs || this.outputs.length == 0)
      +		return null;
      +
      +	if(slot >= this.outputs.length)
      +		return null;
      +
      +	var output = this.outputs[slot];
      +	if(!output.links || output.links.length == 0)
      +		return null;
      +
      +	var r = [];
      +	for(var i = 0; i < output.links.length; i++)
      +	{
      +		var link_id = output.links[i];
      +		var link = this.graph.links[ link_id ];
      +		if(link)
      +		{
      +			var target_node = this.graph.getNodeById( link.target_id );
      +			if( target_node )
      +				r.push( target_node );
      +		}
      +	}
      +	return r;
      +}
      +
      +/**
      +* Triggers an event in this node, this will trigger any output with the same name
      +* @method trigger
      +* @param {String} event name ( "on_play", ... ) if action is equivalent to false then the event is send to all
      +* @param {*} param
      +*/
      +LGraphNode.prototype.trigger = function( action, param )
      +{
      +	if( !this.outputs || !this.outputs.length )
      +		return;
      +
      +	if(this.graph)
      +		this.graph._last_trigger_time = LiteGraph.getTime();
      +
      +	for(var i = 0; i < this.outputs.length; ++i)
      +	{
      +		var output = this.outputs[ i ];
      +		if(!output || output.type !== LiteGraph.EVENT || (action && output.name != action) )
      +			continue;
      +		this.triggerSlot( i, param );
      +	}
      +}
      +
      +/**
      +* Triggers an slot event in this node
      +* @method triggerSlot
      +* @param {Number} slot the index of the output slot
      +* @param {*} param
      +*/
      +LGraphNode.prototype.triggerSlot = function( slot, param )
      +{
      +	if( !this.outputs )
      +		return;
      +
      +	var output = this.outputs[ slot ];
      +	if( !output )
      +		return;
      +
      +	var links = output.links;
      +	if(!links || !links.length)
      +		return;
      +
      +	if(this.graph)
      +		this.graph._last_trigger_time = LiteGraph.getTime();
      +
      +	//for every link attached here
      +	for(var k = 0; k < links.length; ++k)
      +	{
      +		var link_info = this.graph.links[ links[k] ];
      +		if(!link_info) //not connected
      +			continue;
      +		var node = this.graph.getNodeById( link_info.target_id );
      +		if(!node) //node not found?
      +			continue;
      +
      +		//used to mark events in graph
      +		link_info._last_time = LiteGraph.getTime();
      +
      +		var target_connection = node.inputs[ link_info.target_slot ];
      +
      +		if(node.onAction)
      +			node.onAction( target_connection.name, param );
      +		else if(node.mode === LiteGraph.ON_TRIGGER)
      +		{
      +			if(node.onExecute)
      +				node.onExecute(param);
      +		}
      +	}
      +}
      +
      +/**
      +* add a new property to this node
      +* @method addProperty
      +* @param {string} name
      +* @param {*} default_value
      +* @param {string} type string defining the output type ("vec3","number",...)
      +* @param {Object} extra_info this can be used to have special properties of the property (like values, etc)
      +*/
      +LGraphNode.prototype.addProperty = function( name, default_value, type, extra_info )
      +{
      +	var o = { name: name, type: type, default_value: default_value };
      +	if(extra_info)
      +		for(var i in extra_info)
      +			o[i] = extra_info[i];
      +	if(!this.properties_info)
      +		this.properties_info = [];
      +	this.properties_info.push(o);
      +	if(!this.properties)
      +		this.properties = {};
      +	this.properties[ name ] = default_value;
      +	return o;
      +}
      +
      +
      +//connections
      +
      +/**
      +* add a new output slot to use in this node
      +* @method addOutput
      +* @param {string} name
      +* @param {string} type string defining the output type ("vec3","number",...)
      +* @param {Object} extra_info this can be used to have special properties of an output (label, special color, position, etc)
      +*/
      +LGraphNode.prototype.addOutput = function(name,type,extra_info)
      +{
      +	var o = { name: name, type: type, links: null };
      +	if(extra_info)
      +		for(var i in extra_info)
      +			o[i] = extra_info[i];
      +
      +	if(!this.outputs)
      +		this.outputs = [];
      +	this.outputs.push(o);
      +	if(this.onOutputAdded)
      +		this.onOutputAdded(o);
      +	this.size = this.computeSize();
      +	return o;
      +}
      +
      +/**
      +* add a new output slot to use in this node
      +* @method addOutputs
      +* @param {Array} array of triplets like [[name,type,extra_info],[...]]
      +*/
      +LGraphNode.prototype.addOutputs = function(array)
      +{
      +	for(var i = 0; i < array.length; ++i)
      +	{
      +		var info = array[i];
      +		var o = {name:info[0],type:info[1],link:null};
      +		if(array[2])
      +			for(var j in info[2])
      +				o[j] = info[2][j];
      +
      +		if(!this.outputs)
      +			this.outputs = [];
      +		this.outputs.push(o);
      +		if(this.onOutputAdded)
      +			this.onOutputAdded(o);
      +	}
      +
      +	this.size = this.computeSize();
      +}
      +
      +/**
      +* remove an existing output slot
      +* @method removeOutput
      +* @param {number} slot
      +*/
      +LGraphNode.prototype.removeOutput = function(slot)
      +{
      +	this.disconnectOutput(slot);
      +	this.outputs.splice(slot,1);
      +	this.size = this.computeSize();
      +	if(this.onOutputRemoved)
      +		this.onOutputRemoved(slot);
      +}
      +
      +/**
      +* add a new input slot to use in this node
      +* @method addInput
      +* @param {string} name
      +* @param {string} type string defining the input type ("vec3","number",...), it its a generic one use 0
      +* @param {Object} extra_info this can be used to have special properties of an input (label, color, position, etc)
      +*/
      +LGraphNode.prototype.addInput = function(name,type,extra_info)
      +{
      +	type = type || 0;
      +	var o = {name:name,type:type,link:null};
      +	if(extra_info)
      +		for(var i in extra_info)
      +			o[i] = extra_info[i];
      +
      +	if(!this.inputs)
      +		this.inputs = [];
      +	this.inputs.push(o);
      +	this.size = this.computeSize();
      +	if(this.onInputAdded)
      +		this.onInputAdded(o);
      +	return o;
      +}
      +
      +/**
      +* add several new input slots in this node
      +* @method addInputs
      +* @param {Array} array of triplets like [[name,type,extra_info],[...]]
      +*/
      +LGraphNode.prototype.addInputs = function(array)
      +{
      +	for(var i = 0; i < array.length; ++i)
      +	{
      +		var info = array[i];
      +		var o = {name:info[0], type:info[1], link:null};
      +		if(array[2])
      +			for(var j in info[2])
      +				o[j] = info[2][j];
      +
      +		if(!this.inputs)
      +			this.inputs = [];
      +		this.inputs.push(o);
      +		if(this.onInputAdded)
      +			this.onInputAdded(o);
      +	}
      +
      +	this.size = this.computeSize();
      +}
      +
      +/**
      +* remove an existing input slot
      +* @method removeInput
      +* @param {number} slot
      +*/
      +LGraphNode.prototype.removeInput = function(slot)
      +{
      +	this.disconnectInput(slot);
      +	this.inputs.splice(slot,1);
      +	this.size = this.computeSize();
      +	if(this.onInputRemoved)
      +		this.onInputRemoved(slot);
      +}
      +
      +/**
      +* add an special connection to this node (used for special kinds of graphs)
      +* @method addConnection
      +* @param {string} name
      +* @param {string} type string defining the input type ("vec3","number",...)
      +* @param {[x,y]} pos position of the connection inside the node
      +* @param {string} direction if is input or output
      +*/
      +LGraphNode.prototype.addConnection = function(name,type,pos,direction)
      +{
      +	var o = {
      +		name: name,
      +		type: type,
      +		pos: pos,
      +		direction: direction,
      +		links: null
      +	};
      +	this.connections.push( o );
      +	return o;
      +}
      +
      +/**
      +* computes the size of a node according to its inputs and output slots
      +* @method computeSize
      +* @param {number} minHeight
      +* @return {number} the total size
      +*/
      +LGraphNode.prototype.computeSize = function( minHeight, out )
      +{
      +	var rows = Math.max( this.inputs ? this.inputs.length : 1, this.outputs ? this.outputs.length : 1);
      +	var size = out || new Float32Array([0,0]);
      +	rows = Math.max(rows, 1);
      +	var font_size = LiteGraph.NODE_TEXT_SIZE; //although it should be graphcanvas.inner_text_font size
      +	size[1] = (this.constructor.slot_start_y || 0) + rows * (font_size + 1) + ( this.widgets ? this.widgets.length : 0 ) * (LiteGraph.NODE_WIDGET_HEIGHT + 4 ) + 4;
      +
      +	var font_size = font_size;
      +	var title_width = compute_text_size( this.title );
      +	var input_width = 0;
      +	var output_width = 0;
      +
      +	if(this.inputs)
      +		for(var i = 0, l = this.inputs.length; i < l; ++i)
      +		{
      +			var input = this.inputs[i];
      +			var text = input.label || input.name || "";
      +			var text_width = compute_text_size( text );
      +			if(input_width < text_width)
      +				input_width = text_width;
      +		}
      +
      +	if(this.outputs)
      +		for(var i = 0, l = this.outputs.length; i < l; ++i)
      +		{
      +			var output = this.outputs[i];
      +			var text = output.label || output.name || "";
      +			var text_width = compute_text_size( text );
      +			if(output_width < text_width)
      +				output_width = text_width;
      +		}
      +
      +	size[0] = Math.max( input_width + output_width + 10, title_width );
      +	size[0] = Math.max( size[0], LiteGraph.NODE_WIDTH );
      +
      +	if(this.onResize)
      +		this.onResize(size);
      +
      +	function compute_text_size( text )
      +	{
      +		if(!text)
      +			return 0;
      +		return font_size * text.length * 0.6;
      +	}
      +
      +	return size;
      +}
      +
      +/**
      +* Allows to pass 
      +* 
      +* @method addWidget
      +* @return {Float32Array[4]} the total size
      +*/
      +LGraphNode.prototype.addWidget = function( type, name, value, callback, options )
      +{
      +	if(!this.widgets)
      +		this.widgets = [];
      +	var w = {
      +		type: type.toLowerCase(),
      +		name: name,
      +		value: value,
      +		callback: callback,
      +		options: options || {}
      +	};
      +
      +	if(w.options.y !== undefined )
      +		w.y = w.options.y;
      +
      +	if( type == "combo" && !w.options.values )
      +		throw("LiteGraph addWidget('combo',...) requires to pass values in options: { values:['red','blue'] }");
      +	this.widgets.push(w);
      +	return w;
      +}
      +
      +
      +/**
      +* returns the bounding of the object, used for rendering purposes
      +* bounding is: [topleft_cornerx, topleft_cornery, width, height]
      +* @method getBounding
      +* @return {Float32Array[4]} the total size
      +*/
      +LGraphNode.prototype.getBounding = function( out )
      +{
      +	out = out || new Float32Array(4);
      +	out[0] = this.pos[0] - 4;
      +	out[1] = this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT;
      +	out[2] = this.size[0] + 4;
      +	out[3] = this.size[1] + LiteGraph.NODE_TITLE_HEIGHT;
      +	return out;
      +}
      +
      +/**
      +* checks if a point is inside the shape of a node
      +* @method isPointInside
      +* @param {number} x
      +* @param {number} y
      +* @return {boolean}
      +*/
      +LGraphNode.prototype.isPointInside = function(x,y, margin)
      +{
      +	margin = margin || 0;
      +
      +	var margin_top = this.graph && this.graph.isLive() ? 0 : 20;
      +	if(this.flags && this.flags.collapsed)
      +	{
      +		//if ( distance([x,y], [this.pos[0] + this.size[0]*0.5, this.pos[1] + this.size[1]*0.5]) < LiteGraph.NODE_COLLAPSED_RADIUS)
      +		if( isInsideRectangle( x, y, this.pos[0] - margin, this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT - margin, (this._collapsed_width||LiteGraph.NODE_COLLAPSED_WIDTH) + 2 * margin, LiteGraph.NODE_TITLE_HEIGHT + 2 * margin ) )
      +			return true;
      +	}
      +	else if ( (this.pos[0] - 4 - margin) < x && (this.pos[0] + this.size[0] + 4 + margin) > x
      +		&& (this.pos[1] - margin_top - margin) < y && (this.pos[1] + this.size[1] + margin) > y)
      +		return true;
      +	return false;
      +}
      +
      +/**
      +* checks if a point is inside a node slot, and returns info about which slot
      +* @method getSlotInPosition
      +* @param {number} x
      +* @param {number} y
      +* @return {Object} if found the object contains { input|output: slot object, slot: number, link_pos: [x,y] }
      +*/
      +LGraphNode.prototype.getSlotInPosition = function( x, y )
      +{
      +	//search for inputs
      +	if(this.inputs)
      +		for(var i = 0, l = this.inputs.length; i < l; ++i)
      +		{
      +			var input = this.inputs[i];
      +			var link_pos = this.getConnectionPos( true,i );
      +			if( isInsideRectangle(x, y, link_pos[0] - 10, link_pos[1] - 5, 20,10) )
      +				return { input: input, slot: i, link_pos: link_pos, locked: input.locked };
      +		}
      +
      +	if(this.outputs)
      +		for(var i = 0, l = this.outputs.length; i < l; ++i)
      +		{
      +			var output = this.outputs[i];
      +			var link_pos = this.getConnectionPos(false,i);
      +			if( isInsideRectangle(x, y, link_pos[0] - 10, link_pos[1] - 5, 20,10) )
      +				return { output: output, slot: i, link_pos: link_pos, locked: output.locked };
      +		}
      +
      +	return null;
      +}
      +
      +/**
      +* returns the input slot with a given name (used for dynamic slots), -1 if not found
      +* @method findInputSlot
      +* @param {string} name the name of the slot
      +* @return {number} the slot (-1 if not found)
      +*/
      +LGraphNode.prototype.findInputSlot = function(name)
      +{
      +	if(!this.inputs)
      +		return -1;
      +	for(var i = 0, l = this.inputs.length; i < l; ++i)
      +		if(name == this.inputs[i].name)
      +			return i;
      +	return -1;
      +}
      +
      +/**
      +* returns the output slot with a given name (used for dynamic slots), -1 if not found
      +* @method findOutputSlot
      +* @param {string} name the name of the slot
      +* @return {number} the slot (-1 if not found)
      +*/
      +LGraphNode.prototype.findOutputSlot = function(name)
      +{
      +	if(!this.outputs) return -1;
      +	for(var i = 0, l = this.outputs.length; i < l; ++i)
      +		if(name == this.outputs[i].name)
      +			return i;
      +	return -1;
      +}
      +
      +/**
      +* connect this node output to the input of another node
      +* @method connect
      +* @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot)
      +* @param {LGraphNode} node the target node
      +* @param {number_or_string} target_slot the input slot of the target node (could be the number of the slot or the string with the name of the slot, or -1 to connect a trigger)
      +* @return {boolean} if it was connected succesfully
      +*/
      +LGraphNode.prototype.connect = function( slot, target_node, target_slot )
      +{
      +	target_slot = target_slot || 0;
      +
      +	if(!this.graph) //could be connected before adding it to a graph
      +	{
      +		console.log("Connect: Error, node doesnt belong to any graph. Nodes must be added first to a graph before connecting them."); //due to link ids being associated with graphs
      +		return false;
      +	}
      +
      +
      +	//seek for the output slot
      +	if( slot.constructor === String )
      +	{
      +		slot = this.findOutputSlot(slot);
      +		if(slot == -1)
      +		{
      +			if(LiteGraph.debug)
      +				console.log("Connect: Error, no slot of name " + slot);
      +			return false;
      +		}
      +	}
      +	else if(!this.outputs || slot >= this.outputs.length)
      +	{
      +		if(LiteGraph.debug)
      +			console.log("Connect: Error, slot number not found");
      +		return false;
      +	}
      +
      +	if(target_node && target_node.constructor === Number)
      +		target_node = this.graph.getNodeById( target_node );
      +	if(!target_node)
      +		throw("target node is null");
      +
      +	//avoid loopback
      +	if(target_node == this)
      +		return false;
      +
      +	//you can specify the slot by name
      +	if(target_slot.constructor === String)
      +	{
      +		target_slot = target_node.findInputSlot( target_slot );
      +		if(target_slot == -1)
      +		{
      +			if(LiteGraph.debug)
      +				console.log("Connect: Error, no slot of name " + target_slot);
      +			return false;
      +		}
      +	}
      +	else if( target_slot === LiteGraph.EVENT )
      +	{
      +		//search for first slot with event?
      +		/*
      +		//create input for trigger
      +		var input = target_node.addInput("onTrigger", LiteGraph.EVENT );
      +		target_slot = target_node.inputs.length - 1; //last one is the one created
      +		target_node.mode = LiteGraph.ON_TRIGGER;
      +		*/
      +		return false;
      +	}
      +	else if( !target_node.inputs || target_slot >= target_node.inputs.length )
      +	{
      +		if(LiteGraph.debug)
      +			console.log("Connect: Error, slot number not found");
      +		return false;
      +	}
      +
      +	//if there is something already plugged there, disconnect
      +	if(target_node.inputs[ target_slot ].link != null )
      +		target_node.disconnectInput( target_slot );
      +
      +	//why here??
      +	//this.setDirtyCanvas(false,true);
      +	//this.graph.connectionChange( this );
      +
      +	var output = this.outputs[slot];
      +
      +	//allows nodes to block connection
      +	if(target_node.onConnectInput)
      +		if( target_node.onConnectInput( target_slot, output.type, output ) === false)
      +			return false;
      +
      +	var input = target_node.inputs[target_slot];
      +
      +	if( LiteGraph.isValidConnection( output.type, input.type ) )
      +	{
      +		var link_info = {
      +			id: this.graph.last_link_id++,
      +			type: input.type,
      +			origin_id: this.id,
      +			origin_slot: slot,
      +			target_id: target_node.id,
      +			target_slot: target_slot
      +		};
      +
      +		//add to graph links list
      +		this.graph.links[ link_info.id ] = link_info;
      +
      +		//connect in output
      +		if( output.links == null )
      +			output.links = [];
      +		output.links.push( link_info.id );
      +		//connect in input
      +		target_node.inputs[target_slot].link = link_info.id;
      +		if(this.graph)
      +			this.graph._version++;
      +		if(this.onConnectionsChange)
      +			this.onConnectionsChange( LiteGraph.OUTPUT, slot, true, link_info, output ); //link_info has been created now, so its updated
      +		if(target_node.onConnectionsChange)
      +			target_node.onConnectionsChange( LiteGraph.INPUT, target_slot, true, link_info, input );
      +		if( this.graph && this.graph.onNodeConnectionChange )
      +			this.graph.onNodeConnectionChange( LiteGraph.OUTPUT, this, slot, target_node, target_slot );
      +	}
      +
      +	this.setDirtyCanvas(false,true);
      +	this.graph.connectionChange( this );
      +
      +	return true;
      +}
      +
      +/**
      +* disconnect one output to an specific node
      +* @method disconnectOutput
      +* @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot)
      +* @param {LGraphNode} target_node the target node to which this slot is connected [Optional, if not target_node is specified all nodes will be disconnected]
      +* @return {boolean} if it was disconnected succesfully
      +*/
      +LGraphNode.prototype.disconnectOutput = function( slot, target_node )
      +{
      +	if( slot.constructor === String )
      +	{
      +		slot = this.findOutputSlot(slot);
      +		if(slot == -1)
      +		{
      +			if(LiteGraph.debug)
      +				console.log("Connect: Error, no slot of name " + slot);
      +			return false;
      +		}
      +	}
      +	else if(!this.outputs || slot >= this.outputs.length)
      +	{
      +		if(LiteGraph.debug)
      +			console.log("Connect: Error, slot number not found");
      +		return false;
      +	}
      +
      +	//get output slot
      +	var output = this.outputs[slot];
      +	if(!output.links || output.links.length == 0)
      +		return false;
      +
      +	//one of the output links in this slot
      +	if(target_node)
      +	{
      +		if(target_node.constructor === Number)
      +			target_node = this.graph.getNodeById( target_node );
      +		if(!target_node)
      +			throw("Target Node not found");
      +
      +		for(var i = 0, l = output.links.length; i < l; i++)
      +		{
      +			var link_id = output.links[i];
      +			var link_info = this.graph.links[ link_id ];
      +
      +			//is the link we are searching for...
      +			if( link_info.target_id == target_node.id )
      +			{
      +				output.links.splice(i,1); //remove here
      +				var input = target_node.inputs[ link_info.target_slot ];
      +				input.link = null; //remove there
      +				delete this.graph.links[ link_id ]; //remove the link from the links pool
      +				if(this.graph)
      +					this.graph._version++;
      +				if(target_node.onConnectionsChange)
      +					target_node.onConnectionsChange( LiteGraph.INPUT, link_info.target_slot, false, link_info, input ); //link_info hasnt been modified so its ok
      +				if(this.onConnectionsChange)
      +					this.onConnectionsChange( LiteGraph.OUTPUT, slot, false, link_info, output );
      +				if( this.graph && this.graph.onNodeConnectionChange )
      +					this.graph.onNodeConnectionChange( LiteGraph.OUTPUT, this, slot );
      +				if( this.graph && this.graph.onNodeConnectionChange )
      +					this.graph.onNodeConnectionChange( LiteGraph.INPUT, target_node, link_info.target_slot );
      +				break;
      +			}
      +		}
      +	}
      +	else //all the links in this output slot
      +	{
      +		for(var i = 0, l = output.links.length; i < l; i++)
      +		{
      +			var link_id = output.links[i];
      +			var link_info = this.graph.links[ link_id ];
      +			if(!link_info) //bug: it happens sometimes
      +				continue;
      +
      +			var target_node = this.graph.getNodeById( link_info.target_id );
      +			var input = null;
      +			if(this.graph)
      +				this.graph._version++;
      +			if(target_node)
      +			{
      +				input = target_node.inputs[ link_info.target_slot ];
      +				input.link = null; //remove other side link
      +				if(target_node.onConnectionsChange)
      +					target_node.onConnectionsChange( LiteGraph.INPUT, link_info.target_slot, false, link_info, input ); //link_info hasnt been modified so its ok
      +				if( this.graph && this.graph.onNodeConnectionChange )
      +					this.graph.onNodeConnectionChange( LiteGraph.INPUT, target_node, link_info.target_slot );
      +			}
      +			delete this.graph.links[ link_id ]; //remove the link from the links pool
      +			if(this.onConnectionsChange)
      +				this.onConnectionsChange( LiteGraph.OUTPUT, slot, false, link_info, output );
      +			if( this.graph && this.graph.onNodeConnectionChange )
      +				this.graph.onNodeConnectionChange( LiteGraph.OUTPUT, this, slot );
      +		}
      +		output.links = null;
      +	}
      +
      +
      +	this.setDirtyCanvas(false,true);
      +	this.graph.connectionChange( this );
      +	return true;
      +}
      +
      +/**
      +* disconnect one input
      +* @method disconnectInput
      +* @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot)
      +* @return {boolean} if it was disconnected succesfully
      +*/
      +LGraphNode.prototype.disconnectInput = function( slot )
      +{
      +	//seek for the output slot
      +	if( slot.constructor === String )
      +	{
      +		slot = this.findInputSlot(slot);
      +		if(slot == -1)
      +		{
      +			if(LiteGraph.debug)
      +				console.log("Connect: Error, no slot of name " + slot);
      +			return false;
      +		}
      +	}
      +	else if(!this.inputs || slot >= this.inputs.length)
      +	{
      +		if(LiteGraph.debug)
      +			console.log("Connect: Error, slot number not found");
      +		return false;
      +	}
      +
      +	var input = this.inputs[slot];
      +	if(!input)
      +		return false;
      +
      +	var link_id = this.inputs[slot].link;
      +	this.inputs[slot].link = null;
      +
      +	//remove other side
      +	var link_info = this.graph.links[ link_id ];
      +	if( link_info )
      +	{
      +		var target_node = this.graph.getNodeById( link_info.origin_id );
      +		if(!target_node)
      +			return false;
      +
      +		var output = target_node.outputs[ link_info.origin_slot ];
      +		if(!output || !output.links || output.links.length == 0)
      +			return false;
      +
      +		//search in the inputs list for this link
      +		for(var i = 0, l = output.links.length; i < l; i++)
      +		{
      +			if( output.links[i] == link_id )
      +			{
      +				output.links.splice(i,1);
      +				break;
      +			}
      +		}
      +
      +		delete this.graph.links[ link_id ]; //remove from the pool
      +		if(this.graph)
      +			this.graph._version++;
      +		if( this.onConnectionsChange )
      +			this.onConnectionsChange( LiteGraph.INPUT, slot, false, link_info, input );
      +		if( target_node.onConnectionsChange )
      +			target_node.onConnectionsChange( LiteGraph.OUTPUT, i, false, link_info, output );
      +	}
      +
      +	this.setDirtyCanvas(false,true);
      +	this.graph.connectionChange( this );
      +	return true;
      +}
      +
      +/**
      +* returns the center of a connection point in canvas coords
      +* @method getConnectionPos
      +* @param {boolean} is_input true if if a input slot, false if it is an output
      +* @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot)
      +* @return {[x,y]} the position
      +**/
      +LGraphNode.prototype.getConnectionPos = function( is_input, slot_number )
      +{
      +	if(this.flags.collapsed)
      +	{
      +		if(is_input)
      +			return [this.pos[0], this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT * 0.5];
      +		else
      +			return [this.pos[0] + (this._collapsed_width || LiteGraph.NODE_COLLAPSED_WIDTH), this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT * 0.5];
      +	}
      +
      +	if(is_input && slot_number == -1)
      +	{
      +		return [this.pos[0] + 10, this.pos[1] + 10];
      +	}
      +
      +	if(is_input && this.inputs.length > slot_number && this.inputs[slot_number].pos)
      +		return [this.pos[0] + this.inputs[slot_number].pos[0],this.pos[1] + this.inputs[slot_number].pos[1]];
      +	else if(!is_input && this.outputs.length > slot_number && this.outputs[slot_number].pos)
      +		return [this.pos[0] + this.outputs[slot_number].pos[0],this.pos[1] + this.outputs[slot_number].pos[1]];
      +
      +	if(!is_input) //output
      +		return [this.pos[0] + this.size[0] + 1, this.pos[1] + 10 + slot_number * LiteGraph.NODE_SLOT_HEIGHT + (this.constructor.slot_start_y || 0)];
      +	return [this.pos[0] , this.pos[1] + 10 + slot_number * LiteGraph.NODE_SLOT_HEIGHT + (this.constructor.slot_start_y || 0) ];
      +}
      +
      +/* Force align to grid */
      +LGraphNode.prototype.alignToGrid = function()
      +{
      +	this.pos[0] = LiteGraph.CANVAS_GRID_SIZE * Math.round(this.pos[0] / LiteGraph.CANVAS_GRID_SIZE);
      +	this.pos[1] = LiteGraph.CANVAS_GRID_SIZE * Math.round(this.pos[1] / LiteGraph.CANVAS_GRID_SIZE);
      +}
      +
      +
      +/* Console output */
      +LGraphNode.prototype.trace = function(msg)
      +{
      +	if(!this.console)
      +		this.console = [];
      +	this.console.push(msg);
      +	if(this.console.length > LGraphNode.MAX_CONSOLE)
      +		this.console.shift();
      +
      +	this.graph.onNodeTrace(this,msg);
      +}
      +
      +/* Forces to redraw or the main canvas (LGraphNode) or the bg canvas (links) */
      +LGraphNode.prototype.setDirtyCanvas = function(dirty_foreground, dirty_background)
      +{
      +	if(!this.graph)
      +		return;
      +	this.graph.sendActionToCanvas("setDirty",[dirty_foreground, dirty_background]);
      +}
      +
      +LGraphNode.prototype.loadImage = function(url)
      +{
      +	var img = new Image();
      +	img.src = LiteGraph.node_images_path + url;
      +	img.ready = false;
      +
      +	var that = this;
      +	img.onload = function() {
      +		this.ready = true;
      +		that.setDirtyCanvas(true);
      +	}
      +	return img;
      +}
      +
      +//safe LGraphNode action execution (not sure if safe)
      +/*
      +LGraphNode.prototype.executeAction = function(action)
      +{
      +	if(action == "") return false;
      +
      +	if( action.indexOf(";") != -1 || action.indexOf("}") != -1)
      +	{
      +		this.trace("Error: Action contains unsafe characters");
      +		return false;
      +	}
      +
      +	var tokens = action.split("(");
      +	var func_name = tokens[0];
      +	if( typeof(this[func_name]) != "function")
      +	{
      +		this.trace("Error: Action not found on node: " + func_name);
      +		return false;
      +	}
      +
      +	var code = action;
      +
      +	try
      +	{
      +		var _foo = eval;
      +		eval = null;
      +		(new Function("with(this) { " + code + "}")).call(this);
      +		eval = _foo;
      +	}
      +	catch (err)
      +	{
      +		this.trace("Error executing action {" + action + "} :" + err);
      +		return false;
      +	}
      +
      +	return true;
      +}
      +*/
      +
      +/* Allows to get onMouseMove and onMouseUp events even if the mouse is out of focus */
      +LGraphNode.prototype.captureInput = function(v)
      +{
      +	if(!this.graph || !this.graph.list_of_graphcanvas)
      +		return;
      +
      +	var list = this.graph.list_of_graphcanvas;
      +
      +	for(var i = 0; i < list.length; ++i)
      +	{
      +		var c = list[i];
      +		//releasing somebody elses capture?!
      +		if(!v && c.node_capturing_input != this)
      +			continue;
      +
      +		//change
      +		c.node_capturing_input = v ? this : null;
      +	}
      +}
      +
      +/**
      +* Collapse the node to make it smaller on the canvas
      +* @method collapse
      +**/
      +LGraphNode.prototype.collapse = function( force )
      +{
      +	this.graph._version++;
      +	if(this.constructor.collapsable === false && !force)
      +		return;
      +	if(!this.flags.collapsed)
      +		this.flags.collapsed = true;
      +	else
      +		this.flags.collapsed = false;
      +	this.setDirtyCanvas(true,true);
      +}
      +
      +/**
      +* Forces the node to do not move or realign on Z
      +* @method pin
      +**/
      +
      +LGraphNode.prototype.pin = function(v)
      +{
      +	this.graph._version++;
      +	if(v === undefined)
      +		this.flags.pinned = !this.flags.pinned;
      +	else
      +		this.flags.pinned = v;
      +}
      +
      +LGraphNode.prototype.localToScreen = function(x,y, graphcanvas)
      +{
      +	return [(x + this.pos[0]) * graphcanvas.scale + graphcanvas.offset[0],
      +		(y + this.pos[1]) * graphcanvas.scale + graphcanvas.offset[1]];
      +}
      +
      +
      +
      +
      +function LGraphGroup( title )
      +{
      +	this._ctor( title );
      +}
      +
      +global.LGraphGroup = LiteGraph.LGraphGroup = LGraphGroup;
      +
      +LGraphGroup.prototype._ctor = function( title )
      +{
      +	this.title = title || "Group";
      +	this._bounding = new Float32Array([10,10,140,80]);
      +	this._pos = this._bounding.subarray(0,2);
      +	this._size = this._bounding.subarray(2,4);
      +	this._nodes = [];
      +	this.color = LGraphCanvas.node_colors.pale_blue ? LGraphCanvas.node_colors.pale_blue.groupcolor : "#AAA";
      +	this.graph = null;
      +
      +	Object.defineProperty( this, "pos", {
      +		set: function(v)
      +		{
      +			if(!v || v.length < 2)
      +				return;
      +			this._pos[0] = v[0];
      +			this._pos[1] = v[1];
      +		},
      +		get: function()
      +		{
      +			return this._pos;
      +		},
      +		enumerable: true
      +	});
      +
      +	Object.defineProperty( this, "size", {
      +		set: function(v)
      +		{
      +			if(!v || v.length < 2)
      +				return;
      +			this._size[0] = Math.max(140,v[0]);
      +			this._size[1] = Math.max(80,v[1]);
      +		},
      +		get: function()
      +		{
      +			return this._size;
      +		},
      +		enumerable: true
      +	});
      +}
      +
      +LGraphGroup.prototype.configure = function(o)
      +{
      +	this.title = o.title;
      +	this._bounding.set( o.bounding );
      +	this.color = o.color;
      +}
      +
      +LGraphGroup.prototype.serialize = function()
      +{
      +	var b = this._bounding;
      +	return {
      +		title: this.title,
      +		bounding: [ b[0], b[1], b[2], b[3] ],
      +		color: this.color
      +	};
      +}
      +
      +LGraphGroup.prototype.move = function(deltax, deltay, ignore_nodes)
      +{
      +	this._pos[0] += deltax;
      +	this._pos[1] += deltay;
      +	if(ignore_nodes)
      +		return;
      +	for(var i = 0; i < this._nodes.length; ++i)
      +	{
      +		var node = this._nodes[i];
      +		node.pos[0] += deltax;
      +		node.pos[1] += deltay;
      +	}
      +}
      +
      +LGraphGroup.prototype.recomputeInsideNodes = function()
      +{
      +	this._nodes.length = 0;
      +	var nodes = this.graph._nodes;
      +	var node_bounding = new Float32Array(4);
      +
      +	for(var i = 0; i < nodes.length; ++i)
      +	{
      +		var node = nodes[i];
      +		node.getBounding( node_bounding );
      +		if(!overlapBounding( this._bounding, node_bounding ))
      +			continue; //out of the visible area
      +		this._nodes.push( node );
      +	}
      +}
      +
      +LGraphGroup.prototype.isPointInside = LGraphNode.prototype.isPointInside;
      +LGraphGroup.prototype.setDirtyCanvas = LGraphNode.prototype.setDirtyCanvas;
      +
      +//*********************************************************************************
      +// LGraphCanvas: LGraph renderer CLASS
      +//*********************************************************************************
      +
      +/**
      +* This class is in charge of rendering one graph inside a canvas. And provides all the interaction required.
      +* Valid callbacks are: onNodeSelected, onNodeDeselected, onShowNodePanel, onNodeDblClicked
      +*
      +* @class LGraphCanvas
      +* @constructor
      +* @param {HTMLCanvas} canvas the canvas where you want to render (it accepts a selector in string format or the canvas element itself)
      +* @param {LGraph} graph [optional]
      +* @param {Object} options [optional] { skip_rendering, autoresize }
      +*/
      +function LGraphCanvas( canvas, graph, options )
      +{
      +	options = options || {};
      +
      +	//if(graph === undefined)
      +  //	throw ("No graph assigned");
      +	this.background_image = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQBJREFUeNrs1rEKwjAUhlETUkj3vP9rdmr1Ysammk2w5wdxuLgcMHyptfawuZX4pJSWZTnfnu/lnIe/jNNxHHGNn//HNbbv+4dr6V+11uF527arU7+u63qfa/bnmh8sWLBgwYJlqRf8MEptXPBXJXa37BSl3ixYsGDBMliwFLyCV/DeLIMFCxYsWLBMwSt4Be/NggXLYMGCBUvBK3iNruC9WbBgwYJlsGApeAWv4L1ZBgsWLFiwYJmCV/AK3psFC5bBggULloJX8BpdwXuzYMGCBctgwVLwCl7Be7MMFixYsGDBsu8FH1FaSmExVfAxBa/gvVmwYMGCZbBg/W4vAQYA5tRF9QYlv/QAAAAASUVORK5CYII='
      +
      +	if(canvas && canvas.constructor === String )
      +		canvas = document.querySelector( canvas );
      +
      +	this.max_zoom = 10;
      +	this.min_zoom = 0.1;
      +	this.zoom_modify_alpha = true; //otherwise it generates ugly patterns when scaling down too much
      +
      +	this.title_text_font = "bold "+LiteGraph.NODE_TEXT_SIZE+"px Arial";
      +	this.inner_text_font = "normal "+LiteGraph.NODE_SUBTEXT_SIZE+"px Arial";
      +	this.node_title_color = LiteGraph.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 = true;
      +	this.use_gradients = false; //set to true to render titlebar with gradients
      +	this.editor_alpha = 1; //used for transition
      +	this.pause_rendering = false;
      +	this.render_shadows = true;
      +	this.clear_background = true;
      +
      +	this.render_only_selected = true;
      +	this.live_mode = false;
      +	this.show_info = true;
      +	this.allow_dragcanvas = true;
      +	this.allow_dragnodes = true;
      +	this.allow_interaction = true; //allow to control widgets, buttons, collapse, etc
      +	this.allow_searchbox = true;
      +	this.drag_mode = false;
      +	this.dragging_rectangle = null;
      +
      +	this.filter = null; //allows to filter to only accept some type of nodes in a graph
      +
      +	this.always_render_background = false;
      +	this.render_canvas_border = true;
      +	this.render_connections_shadows = false; //too much cpu
      +	this.render_connections_border = true;
      +	this.render_curved_connections = true;
      +	this.render_connection_arrows = true;
      +
      +	this.canvas_mouse = [0,0]; //mouse in canvas graph coordinates, where 0,0 is the top-left corner of the blue rectangle
      +
      +	//to personalize the search box
      +	this.onSearchBox = null;
      +	this.onSearchBoxSelection = null;
      +
      +	this.connections_width = 3;
      +	this.round_radius = 8;
      +
      +	this.current_node = null;
      +	this.node_widget = null; //used for widgets
      +	this.last_mouse_position = [0,0];
      +
      +	//link canvas and graph
      +	if(graph)
      +		graph.attachCanvas(this);
      +
      +	this.setCanvas( canvas );
      +	this.clear();
      +
      +	if(!options.skip_render)
      +		this.startRendering();
      +
      +	this.autoresize = options.autoresize;
      +}
      +
      +global.LGraphCanvas = LiteGraph.LGraphCanvas = LGraphCanvas;
      +
      +LGraphCanvas.link_type_colors = {"-1":"#F85",'number':"#AAC","node":"#DCA"};
      +LGraphCanvas.gradients = {}; //cache of gradients
      +
      +/**
      +* clears all the data inside
      +*
      +* @method clear
      +*/
      +LGraphCanvas.prototype.clear = function()
      +{
      +	this.frame = 0;
      +	this.last_draw_time = 0;
      +	this.render_time = 0;
      +	this.fps = 0;
      +
      +	this.scale = 1;
      +	this.offset = [0,0];
      +
      +	this.dragging_rectangle = null;
      +
      +	this.selected_nodes = {};
      +	this.selected_group = null;
      +
      +	this.visible_nodes = [];
      +	this.node_dragged = null;
      +	this.node_over = null;
      +	this.node_capturing_input = null;
      +	this.connecting_node = null;
      +	this.highlighted_links = {};
      +
      +	this.dirty_canvas = true;
      +	this.dirty_bgcanvas = true;
      +	this.dirty_area = null;
      +
      +	this.node_in_panel = null;
      +	this.node_widget = null;
      +
      +	this.last_mouse = [0,0];
      +	this.last_mouseclick = 0;
      +
      +	if(this.onClear)
      +		this.onClear();
      +	//this.UIinit();
      +}
      +
      +/**
      +* assigns a graph, you can reasign graphs to the same canvas
      +*
      +* @method setGraph
      +* @param {LGraph} graph
      +*/
      +LGraphCanvas.prototype.setGraph = function( graph, skip_clear )
      +{
      +	if(this.graph == graph)
      +		return;
      +
      +	if(!skip_clear)
      +		this.clear();
      +
      +	if(!graph && this.graph)
      +	{
      +		this.graph.detachCanvas(this);
      +		return;
      +	}
      +
      +	/*
      +	if(this.graph)
      +		this.graph.canvas = null; //remove old graph link to the canvas
      +	this.graph = graph;
      +	if(this.graph)
      +		this.graph.canvas = this;
      +	*/
      +	graph.attachCanvas(this);
      +	this.setDirty(true,true);
      +}
      +
      +/**
      +* opens a graph contained inside a node in the current graph
      +*
      +* @method openSubgraph
      +* @param {LGraph} graph
      +*/
      +LGraphCanvas.prototype.openSubgraph = function(graph)
      +{
      +	if(!graph)
      +		throw("graph cannot be null");
      +
      +	if(this.graph == graph)
      +		throw("graph cannot be the same");
      +
      +	this.clear();
      +
      +	if(this.graph)
      +	{
      +		if(!this._graph_stack)
      +			this._graph_stack = [];
      +		this._graph_stack.push(this.graph);
      +	}
      +
      +	graph.attachCanvas(this);
      +	this.setDirty(true,true);
      +}
      +
      +/**
      +* closes a subgraph contained inside a node
      +*
      +* @method closeSubgraph
      +* @param {LGraph} assigns a graph
      +*/
      +LGraphCanvas.prototype.closeSubgraph = function()
      +{
      +	if(!this._graph_stack || this._graph_stack.length == 0)
      +		return;
      +	var graph = this._graph_stack.pop();
      +	this.selected_nodes = {};
      +	this.highlighted_links = {};
      +	graph.attachCanvas(this);
      +	this.setDirty(true,true);
      +}
      +
      +/**
      +* assigns a canvas
      +*
      +* @method setCanvas
      +* @param {Canvas} assigns a canvas (also accepts the ID of the element (not a selector)
      +*/
      +LGraphCanvas.prototype.setCanvas = function( canvas, skip_events )
      +{
      +	var that = this;
      +
      +	if(canvas)
      +	{
      +		if( canvas.constructor === String )
      +		{
      +			canvas = document.getElementById(canvas);
      +			if(!canvas)
      +				throw("Error creating LiteGraph canvas: Canvas not found");
      +		}
      +	}
      +
      +	if(canvas === this.canvas)
      +		return;
      +
      +	if(!canvas && this.canvas)
      +	{
      +		//maybe detach events from old_canvas
      +		if(!skip_events)
      +			this.unbindEvents();
      +	}
      +
      +	this.canvas = canvas;
      +
      +	if(!canvas)
      +		return;
      +
      +	//this.canvas.tabindex = "1000";
      +	canvas.className += " lgraphcanvas";
      +	canvas.data = this;
      +
      +	//bg canvas: used for non changing stuff
      +	this.bgcanvas = null;
      +	if(!this.bgcanvas)
      +	{
      +		this.bgcanvas = document.createElement("canvas");
      +		this.bgcanvas.width = this.canvas.width;
      +		this.bgcanvas.height = this.canvas.height;
      +	}
      +
      +	if(canvas.getContext == null)
      +	{
      +		if( canvas.localName != "canvas" )
      +			throw("Element supplied for LGraphCanvas must be a <canvas> element, you passed a " + canvas.localName );
      +		throw("This browser doesnt support Canvas");
      +	}
      +
      +	var ctx = this.ctx = canvas.getContext("2d");
      +	if(ctx == null)
      +	{
      +		if(!canvas.webgl_enabled)
      +			console.warn("This canvas seems to be WebGL, enabling WebGL renderer");
      +		this.enableWebGL();
      +	}
      +
      +	//input:  (move and up could be unbinded)
      +	this._mousemove_callback = this.processMouseMove.bind(this);
      +	this._mouseup_callback = this.processMouseUp.bind(this);
      +
      +	if(!skip_events)
      +		this.bindEvents();
      +}
      +
      +//used in some events to capture them
      +LGraphCanvas.prototype._doNothing = function doNothing(e) { e.preventDefault(); return false; };
      +LGraphCanvas.prototype._doReturnTrue = function doNothing(e) { e.preventDefault(); return true; };
      +
      +/**
      +* binds mouse, keyboard, touch and drag events to the canvas
      +* @method bindEvents
      +**/
      +LGraphCanvas.prototype.bindEvents = function()
      +{
      +	if(	this._events_binded )
      +	{
      +		console.warn("LGraphCanvas: events already binded");
      +		return;
      +	}
      +
      +	var canvas = this.canvas;
      +	var ref_window = this.getCanvasWindow();
      +	var document = ref_window.document; //hack used when moving canvas between windows
      +
      +	this._mousedown_callback = this.processMouseDown.bind(this);
      +	this._mousewheel_callback = this.processMouseWheel.bind(this);
      +
      +	canvas.addEventListener("mousedown", this._mousedown_callback, true ); //down do not need to store the binded
      +	canvas.addEventListener("mousemove", this._mousemove_callback );
      +	canvas.addEventListener("mousewheel", this._mousewheel_callback, false);
      +
      +	canvas.addEventListener("contextmenu", this._doNothing );
      +	canvas.addEventListener("DOMMouseScroll", this._mousewheel_callback, false);
      +
      +	//touch events
      +	//if( 'touchstart' in document.documentElement )
      +	{
      +		canvas.addEventListener("touchstart", this.touchHandler, true);
      +		canvas.addEventListener("touchmove", this.touchHandler, true);
      +		canvas.addEventListener("touchend", this.touchHandler, true);
      +		canvas.addEventListener("touchcancel", this.touchHandler, true);
      +	}
      +
      +	//Keyboard ******************
      +	this._key_callback = this.processKey.bind(this);
      +
      +	canvas.addEventListener("keydown", this._key_callback, true );
      +	document.addEventListener("keyup", this._key_callback, true ); //in document, otherwise it doesnt fire keyup
      +
      +	//Droping Stuff over nodes ************************************
      +	this._ondrop_callback = this.processDrop.bind(this);
      +
      +	canvas.addEventListener("dragover", this._doNothing, false );
      +	canvas.addEventListener("dragend", this._doNothing, false );
      +	canvas.addEventListener("drop", this._ondrop_callback, false );
      +	canvas.addEventListener("dragenter", this._doReturnTrue, false );
      +
      +	this._events_binded = true;
      +}
      +
      +/**
      +* unbinds mouse events from the canvas
      +* @method unbindEvents
      +**/
      +LGraphCanvas.prototype.unbindEvents = function()
      +{
      +	if(	!this._events_binded )
      +	{
      +		console.warn("LGraphCanvas: no events binded");
      +		return;
      +	}
      +
      +	var ref_window = this.getCanvasWindow();
      +	var document = ref_window.document;
      +
      +	this.canvas.removeEventListener( "mousedown", this._mousedown_callback );
      +	this.canvas.removeEventListener( "mousewheel", this._mousewheel_callback );
      +	this.canvas.removeEventListener( "DOMMouseScroll", this._mousewheel_callback );
      +	this.canvas.removeEventListener( "keydown", this._key_callback );
      +	document.removeEventListener( "keyup", this._key_callback );
      +	this.canvas.removeEventListener( "contextmenu", this._doNothing );
      +	this.canvas.removeEventListener( "drop", this._ondrop_callback );
      +	this.canvas.removeEventListener( "dragenter", this._doReturnTrue );
      +
      +	this.canvas.removeEventListener("touchstart", this.touchHandler );
      +	this.canvas.removeEventListener("touchmove", this.touchHandler );
      +	this.canvas.removeEventListener("touchend", this.touchHandler );
      +	this.canvas.removeEventListener("touchcancel", this.touchHandler );
      +
      +	this._mousedown_callback = null;
      +	this._mousewheel_callback = null;
      +	this._key_callback = null;
      +	this._ondrop_callback = null;
      +
      +	this._events_binded = false;
      +}
      +
      +LGraphCanvas.getFileExtension = function (url)
      +{
      +	var question = url.indexOf("?");
      +	if(question != -1)
      +		url = url.substr(0,question);
      +	var point = url.lastIndexOf(".");
      +	if(point == -1)
      +		return "";
      +	return url.substr(point+1).toLowerCase();
      +}
      +
      +/**
      +* this function allows to render the canvas using WebGL instead of Canvas2D
      +* this is useful if you plant to render 3D objects inside your nodes, it uses litegl.js for webgl and canvas2DtoWebGL to emulate the Canvas2D calls in webGL
      +* @method enableWebGL
      +**/
      +LGraphCanvas.prototype.enableWebGL = function()
      +{
      +	if(typeof(GL) === undefined)
      +		throw("litegl.js must be included to use a WebGL canvas");
      +	if(typeof(enableWebGLCanvas) === undefined)
      +		throw("webglCanvas.js must be included to use this feature");
      +
      +	this.gl = this.ctx = enableWebGLCanvas(this.canvas);
      +	this.ctx.webgl = true;
      +	this.bgcanvas = this.canvas;
      +	this.bgctx = this.gl;
      +	this.canvas.webgl_enabled = true;
      +
      +	/*
      +	GL.create({ canvas: this.bgcanvas });
      +	this.bgctx = enableWebGLCanvas( this.bgcanvas );
      +	window.gl = this.gl;
      +	*/
      +}
      +
      +
      +/**
      +* marks as dirty the canvas, this way it will be rendered again
      +*
      +* @class LGraphCanvas
      +* @method setDirty
      +* @param {bool} fgcanvas if the foreground canvas is dirty (the one containing the nodes)
      +* @param {bool} bgcanvas if the background canvas is dirty (the one containing the wires)
      +*/
      +LGraphCanvas.prototype.setDirty = function( fgcanvas, bgcanvas )
      +{
      +	if(fgcanvas)
      +		this.dirty_canvas = true;
      +	if(bgcanvas)
      +		this.dirty_bgcanvas = true;
      +}
      +
      +/**
      +* Used to attach the canvas in a popup
      +*
      +* @method getCanvasWindow
      +* @return {window} returns the window where the canvas is attached (the DOM root node)
      +*/
      +LGraphCanvas.prototype.getCanvasWindow = function()
      +{
      +	if(!this.canvas)
      +		return window;
      +	var doc = this.canvas.ownerDocument;
      +	return doc.defaultView || doc.parentWindow;
      +}
      +
      +/**
      +* starts rendering the content of the canvas when needed
      +*
      +* @method startRendering
      +*/
      +LGraphCanvas.prototype.startRendering = function()
      +{
      +	if(this.is_rendering)
      +		return; //already rendering
      +
      +	this.is_rendering = true;
      +	renderFrame.call(this);
      +
      +	function renderFrame()
      +	{
      +		if(!this.pause_rendering)
      +			this.draw();
      +
      +		var window = this.getCanvasWindow();
      +		if(this.is_rendering)
      +			window.requestAnimationFrame( renderFrame.bind(this) );
      +	}
      +}
      +
      +/**
      +* stops rendering the content of the canvas (to save resources)
      +*
      +* @method stopRendering
      +*/
      +LGraphCanvas.prototype.stopRendering = function()
      +{
      +	this.is_rendering = false;
      +	/*
      +	if(this.rendering_timer_id)
      +	{
      +		clearInterval(this.rendering_timer_id);
      +		this.rendering_timer_id = null;
      +	}
      +	*/
      +}
      +
      +/* LiteGraphCanvas input */
      +
      +LGraphCanvas.prototype.processMouseDown = function(e)
      +{
      +	if(!this.graph)
      +		return;
      +
      +	this.adjustMouseEvent(e);
      +
      +	var ref_window = this.getCanvasWindow();
      +	var document = ref_window.document;
      +	LGraphCanvas.active_canvas = this;
      +	var that = this;
      +
      +	//move mouse move event to the window in case it drags outside of the canvas
      +	this.canvas.removeEventListener("mousemove", this._mousemove_callback );
      +	ref_window.document.addEventListener("mousemove", this._mousemove_callback, true ); //catch for the entire window
      +	ref_window.document.addEventListener("mouseup", this._mouseup_callback, true );
      +
      +	var node = this.graph.getNodeOnPos( e.canvasX, e.canvasY, this.visible_nodes );
      +	var skip_dragging = false;
      +	var skip_action = false;
      +	var now = LiteGraph.getTime();
      +	var is_double_click = (now - this.last_mouseclick) < 300;
      +
      +	this.canvas_mouse[0] = e.canvasX;
      +	this.canvas_mouse[1] = e.canvasY;
      +
      +    LiteGraph.closeAllContextMenus( ref_window );
      +
      +	if(e.which == 1) //left button mouse
      +	{
      +		if( e.ctrlKey )
      +		{
      +			this.dragging_rectangle = new Float32Array(4);
      +			this.dragging_rectangle[0] = e.canvasX;
      +			this.dragging_rectangle[1] = e.canvasY;
      +			this.dragging_rectangle[2] = 1;
      +			this.dragging_rectangle[3] = 1;
      +			skip_action = true;
      +		}
      +
      +		var clicking_canvas_bg = false;
      +
      +		//when clicked on top of a node
      +		//and it is not interactive
      +		if( node && this.allow_interaction && !skip_action )
      +		{
      +			if( !this.live_mode && !node.flags.pinned )
      +				this.bringToFront( node ); //if it wasnt selected?
      +
      +			//not dragging mouse to connect two slots
      +			if(!this.connecting_node && !node.flags.collapsed && !this.live_mode)
      +			{
      +				//search for outputs
      +				if(node.outputs)
      +					for(var i = 0, l = node.outputs.length; i < l; ++i)
      +					{
      +						var output = node.outputs[i];
      +						var link_pos = node.getConnectionPos(false,i);
      +						if( isInsideRectangle( e.canvasX, e.canvasY, link_pos[0] - 10, link_pos[1] - 5, 20,10) )
      +						{
      +							this.connecting_node = node;
      +							this.connecting_output = output;
      +							this.connecting_pos = node.getConnectionPos(false,i);
      +							this.connecting_slot = i;
      +
      +                            if (is_double_click) {
      +                                if (node.onOutputDblClick)
      +                                    node.onOutputDblClick(i, e);
      +                            } else {
      +                                if (node.onOutputClick)
      +                                    node.onOutputClick(i, e);
      +                            }
      +
      +							skip_action = true;
      +							break;
      +						}
      +					}
      +
      +				//search for inputs
      +				if(node.inputs)
      +					for(var i = 0, l = node.inputs.length; i < l; ++i)
      +					{
      +						var input = node.inputs[i];
      +						var link_pos = node.getConnectionPos( true, i );
      +						if( isInsideRectangle(e.canvasX, e.canvasY, link_pos[0] - 10, link_pos[1] - 5, 20,10) )
      +						{
      +                            if (is_double_click) {
      +                                if (node.onInputDblClick)
      +                                    node.onInputDblClick(i, e);
      +                            } else {
      +                                if (node.onInputClick)
      +                                    node.onInputClick(i, e);
      +                            }
      +
      +							if(input.link !== null)
      +							{
      +								node.disconnectInput(i);
      +								this.dirty_bgcanvas = true;
      +								skip_action = true;
      +							}
      +						}
      +					}
      +
      +				//Search for corner
      +				if( !skip_action && node.flags.resizable !== false && isInsideRectangle(e.canvasX, e.canvasY, node.pos[0] + node.size[0] - 5, node.pos[1] + node.size[1] - 5 ,5,5 ))
      +				{
      +					this.resizing_node = node;
      +					this.canvas.style.cursor = "se-resize";
      +					skip_action = true;
      +				}
      +			}
      +
      +			//Search for corner
      +			if( !skip_action && isInsideRectangle(e.canvasX, e.canvasY, node.pos[0], node.pos[1] - LiteGraph.NODE_TITLE_HEIGHT, LiteGraph.NODE_TITLE_HEIGHT, LiteGraph.NODE_TITLE_HEIGHT ))
      +			{
      +				node.collapse();
      +				skip_action = true;
      +			}
      +
      +			//it wasnt clicked on the links boxes
      +			if(!skip_action)
      +			{
      +				var block_drag_node = false;
      +
      +				//widgets
      +				var widget = this.processNodeWidgets( node, this.canvas_mouse, e );
      +				if(widget)
      +				{
      +					block_drag_node = true;
      +					this.node_widget = [node, widget];
      +				}
      +
      +				//double clicking
      +				if (is_double_click && this.selected_nodes[ node.id ])
      +				{
      +					//double click node
      +					if( node.onDblClick)
      +						node.onDblClick(e);
      +					this.processNodeDblClicked( node );
      +					block_drag_node = true;
      +				}
      +
      +				//if do not capture mouse
      +				if( node.onMouseDown && node.onMouseDown( e, [e.canvasX - node.pos[0], e.canvasY - node.pos[1]] ) )
      +					block_drag_node = true;
      +				else if(this.live_mode)
      +				{
      +					clicking_canvas_bg = true;
      +					block_drag_node = true;
      +				}
      +
      +				if(!block_drag_node)
      +				{
      +					if(this.allow_dragnodes)
      +						this.node_dragged = node;
      +					if(!this.selected_nodes[ node.id ])
      +						this.processNodeSelected( node, e );
      +				}
      +
      +				this.dirty_canvas = true;
      +			}
      +		}
      +		else //clicked outside of nodes
      +		{
      +			this.selected_group = this.graph.getGroupOnPos( e.canvasX, e.canvasY );
      +			this.selected_group_resizing = false;
      +			if( this.selected_group )
      +			{
      +				if( e.ctrlKey )
      +					this.dragging_rectangle = null;
      +
      +				var dist = distance( [e.canvasX, e.canvasY], [ this.selected_group.pos[0] + this.selected_group.size[0], this.selected_group.pos[1] + this.selected_group.size[1] ] );
      +				if( (dist * this.scale) < 10 )
      +					this.selected_group_resizing = true;
      +				else
      +					this.selected_group.recomputeInsideNodes();
      +			}
      +
      +			if( is_double_click )
      +				this.showSearchBox( e );
      +			
      +			clicking_canvas_bg = true;
      +		}
      +
      +		if( !skip_action && clicking_canvas_bg && this.allow_dragcanvas )
      +		{
      +			this.dragging_canvas = true;
      +		}
      +	}
      +	else if (e.which == 2) //middle button
      +	{
      +
      +	}
      +	else if (e.which == 3) //right button
      +	{
      +		this.processContextMenu( node, e );
      +	}
      +
      +	//TODO
      +	//if(this.node_selected != prev_selected)
      +	//	this.onNodeSelectionChange(this.node_selected);
      +
      +	this.last_mouse[0] = e.localX;
      +	this.last_mouse[1] = e.localY;
      +	this.last_mouseclick = LiteGraph.getTime();
      +
      +	/*
      +	if( (this.dirty_canvas || this.dirty_bgcanvas) && this.rendering_timer_id == null)
      +		this.draw();
      +	*/
      +
      +	this.graph.change();
      +
      +	//this is to ensure to defocus(blur) if a text input element is on focus
      +	if(!ref_window.document.activeElement || (ref_window.document.activeElement.nodeName.toLowerCase() != "input" && ref_window.document.activeElement.nodeName.toLowerCase() != "textarea"))
      +		e.preventDefault();
      +	e.stopPropagation();
      +
      +	if(this.onMouseDown)
      +		this.onMouseDown(e);
      +
      +	return false;
      +}
      +
      +/**
      +* Called when a mouse move event has to be processed
      +* @method processMouseMove
      +**/
      +LGraphCanvas.prototype.processMouseMove = function(e)
      +{
      +	if(this.autoresize)
      +		this.resize();
      +
      +	if(!this.graph)
      +		return;
      +
      +	LGraphCanvas.active_canvas = this;
      +	this.adjustMouseEvent(e);
      +	var mouse = [e.localX, e.localY];
      +	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;
      +
      +	if( this.node_widget )
      +	{
      +		this.processNodeWidgets( this.node_widget[0], this.canvas_mouse, e, this.node_widget[1] );
      +		this.dirty_canvas = true;
      +	}
      +
      +	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)
      +	{
      +		if( this.selected_group_resizing )
      +			this.selected_group.size = [ e.canvasX - this.selected_group.pos[0], e.canvasY - this.selected_group.pos[1] ];
      +		else
      +		{
      +			var deltax = delta[0] / this.scale;
      +			var deltay = delta[1] / this.scale;
      +			this.selected_group.move( deltax, deltay, e.ctrlKey );
      +			if( this.selected_group._nodes.length)
      +				this.dirty_canvas = true;
      +		}
      +		this.dirty_bgcanvas = true;
      +	}
      +	else if(this.dragging_canvas)
      +	{
      +		this.offset[0] += delta[0] / this.scale;
      +		this.offset[1] += delta[1] / this.scale;
      +		this.dirty_canvas = true;
      +		this.dirty_bgcanvas = true;
      +	}
      +	else if(this.allow_interaction)
      +	{
      +		if(this.connecting_node)
      +			this.dirty_canvas = true;
      +
      +		//get node over
      +		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])
      +			{
      +				//mouse leave
      +				this.graph._nodes[i].mouseOver = false;
      +				if(this.node_over && this.node_over.onMouseLeave)
      +					this.node_over.onMouseLeave(e);
      +				this.node_over = null;
      +				this.dirty_canvas = true;
      +			}
      +		}
      +
      +		//mouse over a node
      +		if(node)
      +		{
      +			//this.canvas.style.cursor = "move";
      +			if(!node.mouseOver)
      +			{
      +				//mouse enter
      +				node.mouseOver = true;
      +				this.node_over = node;
      +				this.dirty_canvas = true;
      +
      +				if(node.onMouseEnter) node.onMouseEnter(e);
      +			}
      +
      +			//in case the node wants to do something
      +			if(node.onMouseMove)
      +				node.onMouseMove(e);
      +
      +			//if dragging a link 
      +			if(this.connecting_node)
      +			{
      +				var pos = this._highlight_input || [0,0]; //to store the output of isOverNodeInput
      +
      +				//on top of input
      +				if( this.isOverNodeBox( node, e.canvasX, e.canvasY ) )
      +				{
      +					//mouse on top of the corner box, dont know what to do
      +				}
      +				else
      +				{
      +					//check if I have a slot below de mouse
      +					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 ) )
      +							this._highlight_input = pos;
      +					}
      +					else
      +						this._highlight_input = null;
      +				}
      +			}
      +
      +			//Search for corner
      +			if(this.canvas)
      +			{
      +				if( isInsideRectangle(e.canvasX, e.canvasY, node.pos[0] + node.size[0] - 5, node.pos[1] + node.size[1] - 5 ,5,5 ))
      +					this.canvas.style.cursor = "se-resize";
      +				else
      +					this.canvas.style.cursor = null;
      +			}
      +		}
      +		else if(this.canvas)
      +			this.canvas.style.cursor = null;
      +
      +		if(this.node_capturing_input && this.node_capturing_input != node && this.node_capturing_input.onMouseMove)
      +		{
      +			this.node_capturing_input.onMouseMove(e);
      +		}
      +
      +
      +		if(this.node_dragged && !this.live_mode)
      +		{
      +			/*
      +			this.node_dragged.pos[0] += delta[0] / this.scale;
      +			this.node_dragged.pos[1] += delta[1] / this.scale;
      +			this.node_dragged.pos[0] = Math.round(this.node_dragged.pos[0]);
      +			this.node_dragged.pos[1] = Math.round(this.node_dragged.pos[1]);
      +			*/
      +
      +			for(var i in this.selected_nodes)
      +			{
      +				var n = this.selected_nodes[i];
      +
      +				n.pos[0] += delta[0] / this.scale;
      +				n.pos[1] += delta[1] / this.scale;
      +				//n.pos[0] = Math.round(n.pos[0]);
      +				//n.pos[1] = Math.round(n.pos[1]);
      +			}
      +
      +			this.dirty_canvas = true;
      +			this.dirty_bgcanvas = true;
      +		}
      +
      +		if(this.resizing_node && !this.live_mode)
      +		{
      +			//convert mouse to node space
      +			this.resizing_node.size[0] = e.canvasX - this.resizing_node.pos[0];
      +			this.resizing_node.size[1] = e.canvasY - this.resizing_node.pos[1];
      +
      +			//constraint size
      +			var max_slots = Math.max( this.resizing_node.inputs ? this.resizing_node.inputs.length : 0, this.resizing_node.outputs ? this.resizing_node.outputs.length : 0);
      +			var min_height = max_slots * LiteGraph.NODE_SLOT_HEIGHT + ( this.resizing_node.widgets ? this.resizing_node.widgets.length : 0 ) * (LiteGraph.NODE_WIDGET_HEIGHT + 4 ) + 4;
      +			if(this.resizing_node.size[1] < min_height )
      +				this.resizing_node.size[1] = min_height;
      +			if(this.resizing_node.size[0] < LiteGraph.NODE_MIN_WIDTH)
      +				this.resizing_node.size[0] = LiteGraph.NODE_MIN_WIDTH;
      +
      +			this.canvas.style.cursor = "se-resize";
      +			this.dirty_canvas = true;
      +			this.dirty_bgcanvas = true;
      +		}
      +	}
      +
      +	/*
      +	if((this.dirty_canvas || this.dirty_bgcanvas) && this.rendering_timer_id == null)
      +		this.draw();
      +	*/
      +
      +	e.preventDefault();
      +	//e.stopPropagation();
      +	return false;
      +	//this is not really optimal
      +	//this.graph.change();
      +}
      +
      +/**
      +* Called when a mouse up event has to be processed
      +* @method processMouseUp
      +**/
      +LGraphCanvas.prototype.processMouseUp = function(e)
      +{
      +	if(!this.graph)
      +		return;
      +
      +	var window = this.getCanvasWindow();
      +	var document = window.document;
      +	LGraphCanvas.active_canvas = this;
      +
      +	//restore the mousemove event back to the canvas
      +	document.removeEventListener("mousemove", this._mousemove_callback, true );
      +	this.canvas.addEventListener("mousemove", this._mousemove_callback, true);
      +	document.removeEventListener("mouseup", this._mouseup_callback, true );
      +
      +	this.adjustMouseEvent(e);
      +	var now = LiteGraph.getTime();
      +	e.click_time = (now - this.last_mouseclick);
      +
      +	if (e.which == 1) //left button
      +	{
      +		this.node_widget = null;
      +		this.selected_group = null;
      +		this.selected_group_resizing = false;
      +
      +		if( this.dragging_rectangle )
      +		{
      +			if(this.graph)
      +			{
      +				var nodes = this.graph._nodes;
      +				var node_bounding = new Float32Array(4);
      +				this.deselectAllNodes();
      +				if( this.dragging_rectangle[2] < 0 ) //flip if negative width
      +					this.dragging_rectangle[0] += this.dragging_rectangle[2];
      +				if( this.dragging_rectangle[3] < 0 ) //flip if negative height
      +					this.dragging_rectangle[1] += this.dragging_rectangle[3];
      +				this.dragging_rectangle[2] = Math.abs( this.dragging_rectangle[2] * this.scale ); //abs to convert negative width
      +				this.dragging_rectangle[3] = Math.abs( this.dragging_rectangle[3] * this.scale ); //abs to convert negative height
      +
      +				for(var i = 0; i < nodes.length; ++i)
      +				{
      +					var node = nodes[i];
      +					node.getBounding( node_bounding );
      +					if(!overlapBounding( this.dragging_rectangle, node_bounding ))
      +						continue; //out of the visible area
      +					this.selectNode( node, true );
      +				}
      +			}
      +			this.dragging_rectangle = null;
      +		}
      +		else if(this.connecting_node) //dragging a connection
      +		{
      +			this.dirty_canvas = true;
      +			this.dirty_bgcanvas = true;
      +
      +			var node = this.graph.getNodeOnPos( e.canvasX, e.canvasY, this.visible_nodes );
      +
      +			//node below mouse
      +			if(node)
      +			{
      +				if( this.connecting_output.type == LiteGraph.EVENT && this.isOverNodeBox( node, e.canvasX, e.canvasY ) )
      +				{
      +					this.connecting_node.connect( this.connecting_slot, node, LiteGraph.EVENT );
      +				}
      +				else
      +				{
      +					//slot below mouse? connect
      +					var slot = this.isOverNodeInput(node, e.canvasX, e.canvasY);
      +					if(slot != -1)
      +					{
      +						this.connecting_node.connect(this.connecting_slot, node, slot);
      +					}
      +					else
      +					{ //not on top of an input
      +						var input = node.getInputInfo(0);
      +						//auto connect
      +						if(this.connecting_output.type == LiteGraph.EVENT)
      +							this.connecting_node.connect( this.connecting_slot, node, LiteGraph.EVENT );
      +						else
      +							if(input && !input.link && LiteGraph.isValidConnection( input.type && this.connecting_output.type ) )
      +								this.connecting_node.connect( this.connecting_slot, node, 0 );
      +					}
      +				}
      +			}
      +
      +			this.connecting_output = null;
      +			this.connecting_pos = null;
      +			this.connecting_node = null;
      +			this.connecting_slot = -1;
      +
      +		}//not dragging connection
      +		else if(this.resizing_node)
      +		{
      +			this.dirty_canvas = true;
      +			this.dirty_bgcanvas = true;
      +			this.resizing_node = null;
      +		}
      +		else if(this.node_dragged) //node being dragged?
      +		{
      +			this.dirty_canvas = true;
      +			this.dirty_bgcanvas = true;
      +			this.node_dragged.pos[0] = Math.round(this.node_dragged.pos[0]);
      +			this.node_dragged.pos[1] = Math.round(this.node_dragged.pos[1]);
      +			if(this.graph.config.align_to_grid)
      +				this.node_dragged.alignToGrid();
      +			this.node_dragged = null;
      +		}
      +		else //no node being dragged
      +		{
      +			//get node over
      +			var node = this.graph.getNodeOnPos( e.canvasX, e.canvasY, this.visible_nodes );
      +			if ( !node && e.click_time < 300 )
      +				this.deselectAllNodes();
      +
      +			this.dirty_canvas = true;
      +			this.dragging_canvas = false;
      +
      +			if( this.node_over && this.node_over.onMouseUp )
      +				this.node_over.onMouseUp(e, [e.canvasX - this.node_over.pos[0], e.canvasY - this.node_over.pos[1]] );
      +			if( this.node_capturing_input && this.node_capturing_input.onMouseUp )
      +				this.node_capturing_input.onMouseUp(e, [e.canvasX - this.node_capturing_input.pos[0], e.canvasY - this.node_capturing_input.pos[1]] );
      +		}
      +	}
      +	else if (e.which == 2) //middle button
      +	{
      +		//trace("middle");
      +		this.dirty_canvas = true;
      +		this.dragging_canvas = false;
      +	}
      +	else if (e.which == 3) //right button
      +	{
      +		//trace("right");
      +		this.dirty_canvas = true;
      +		this.dragging_canvas = false;
      +	}
      +
      +	/*
      +	if((this.dirty_canvas || this.dirty_bgcanvas) && this.rendering_timer_id == null)
      +		this.draw();
      +	*/
      +
      +	this.graph.change();
      +
      +	e.stopPropagation();
      +	e.preventDefault();
      +	return false;
      +}
      +
      +/**
      +* Called when a mouse wheel event has to be processed
      +* @method processMouseWheel
      +**/
      +LGraphCanvas.prototype.processMouseWheel = function(e)
      +{
      +	if(!this.graph || !this.allow_dragcanvas)
      +		return;
      +
      +	var delta = (e.wheelDeltaY != null ? e.wheelDeltaY : e.detail * -60);
      +
      +	this.adjustMouseEvent(e);
      +
      +	var zoom = this.scale;
      +
      +	if (delta > 0)
      +		zoom *= 1.1;
      +	else if (delta < 0)
      +		zoom *= 1/(1.1);
      +
      +	this.setZoom( zoom, [ e.localX, e.localY ] );
      +
      +	/*
      +	if(this.rendering_timer_id == null)
      +		this.draw();
      +	*/
      +
      +	this.graph.change();
      +
      +	e.preventDefault();
      +	return false; // prevent default
      +}
      +
      +/**
      +* retuns true if a position (in graph space) is on top of a node little corner box
      +* @method isOverNodeBox
      +**/
      +LGraphCanvas.prototype.isOverNodeBox = function( node, canvasx, canvasy )
      +{
      +	var title_height = LiteGraph.NODE_TITLE_HEIGHT;
      +	if( isInsideRectangle( canvasx, canvasy, node.pos[0] + 2, node.pos[1] + 2 - title_height, title_height - 4,title_height - 4) )
      +		return true;
      +	return false;
      +}
      +
      +/**
      +* retuns true if a position (in graph space) is on top of a node input slot
      +* @method isOverNodeInput
      +**/
      +LGraphCanvas.prototype.isOverNodeInput = function(node, canvasx, canvasy, slot_pos )
      +{
      +	if(node.inputs)
      +		for(var i = 0, l = node.inputs.length; i < l; ++i)
      +		{
      +			var input = node.inputs[i];
      +			var link_pos = node.getConnectionPos(true,i);
      +			if( isInsideRectangle(canvasx, canvasy, link_pos[0] - 10, link_pos[1] - 5, 20,10) )
      +			{
      +				if(slot_pos)
      +				{
      +					slot_pos[0] = link_pos[0];
      +					slot_pos[1] = link_pos[1];
      +				}
      +				return i;
      +			}
      +		}
      +	return -1;
      +}
      +
      +/**
      +* process a key event
      +* @method processKey
      +**/
      +LGraphCanvas.prototype.processKey = function(e)
      +{
      +	if(!this.graph)
      +		return;
      +
      +	var block_default = false;
      +	//console.log(e); //debug
      +
      +	if(e.target.localName == "input")
      +		return;
      +
      +	if(e.type == "keydown")
      +	{
      +		if(e.keyCode == 32)
      +		{
      +			this.dragging_canvas = true;
      +			block_default = true;
      +		}
      +
      +		//select all Control A
      +		if(e.keyCode == 65 && e.ctrlKey)
      +		{
      +			this.selectNodes();
      +			block_default = true;
      +		}
      +
      +		if(e.code == "KeyC" && (e.metaKey || e.ctrlKey) && !e.shiftKey ) //copy
      +		{
      +			if(this.selected_nodes)
      +			{
      +				this.copyToClipboard();
      +				block_default = true;
      +			}
      +		}
      +
      +		if(e.code == "KeyV" && (e.metaKey || e.ctrlKey) && !e.shiftKey ) //paste
      +		{
      +			this.pasteFromClipboard();
      +		}
      +
      +		//delete or backspace
      +		if(e.keyCode == 46 || e.keyCode == 8)
      +		{
      +			this.deleteSelectedNodes();
      +			block_default = true;
      +		}
      +
      +		//collapse
      +		//...
      +
      +		//TODO
      +		if(this.selected_nodes)
      +			for (var i in this.selected_nodes)
      +				if(this.selected_nodes[i].onKeyDown)
      +					this.selected_nodes[i].onKeyDown(e);
      +	}
      +	else if( e.type == "keyup" )
      +	{
      +		if(e.keyCode == 32)
      +			this.dragging_canvas = false;
      +
      +		if(this.selected_nodes)
      +			for (var i in this.selected_nodes)
      +				if(this.selected_nodes[i].onKeyUp)
      +					this.selected_nodes[i].onKeyUp(e);
      +	}
      +
      +	this.graph.change();
      +
      +	if(block_default)
      +	{
      +		e.preventDefault();
      +		return false;
      +	}
      +}
      +
      +LGraphCanvas.prototype.copyToClipboard = function()
      +{
      +	var clipboard_info = {
      +		nodes: [],
      +		links: []
      +	};
      +	var index = 0;
      +	var selected_nodes_array = [];
      +	for(var i in this.selected_nodes)
      +	{
      +		var node = this.selected_nodes[i];
      +		node._relative_id = index;
      +		selected_nodes_array.push( node );
      +		index += 1;
      +	}
      +
      +	for(var i = 0; i < selected_nodes_array.length; ++i)
      +	{
      +		var node = selected_nodes_array[i];
      +		clipboard_info.nodes.push( node.clone().serialize() );
      +		if(node.inputs && node.inputs.length)
      +			for(var j = 0; j < node.inputs.length; ++j)
      +			{
      +				var input = node.inputs[j];
      +				if(!input || input.link == null)
      +					continue;
      +				var link_info = this.graph.links[ input.link ];
      +				if(!link_info)
      +					continue;
      +				var target_node = this.graph.getNodeById( link_info.origin_id );
      +				if(!target_node || !this.selected_nodes[ target_node.id ] ) //improve this by allowing connections to non-selected nodes
      +					continue; //not selected
      +				clipboard_info.links.push([ target_node._relative_id, j, node._relative_id, link_info.target_slot ]);
      +			}
      +	}
      +	localStorage.setItem( "litegrapheditor_clipboard", JSON.stringify( clipboard_info ) );
      +}
      +
      +LGraphCanvas.prototype.pasteFromClipboard = function()
      +{
      +	var data = localStorage.getItem( "litegrapheditor_clipboard" );
      +	if(!data)
      +		return;
      +
      +	//create nodes
      +	var clipboard_info = JSON.parse(data);
      +	var nodes = [];
      +	for(var i = 0; i < clipboard_info.nodes.length; ++i)
      +	{
      +		var node_data = clipboard_info.nodes[i];
      +		var node = LiteGraph.createNode( node_data.type );
      +		if(node)
      +		{
      +			node.configure(node_data);
      +			node.pos[0] += 5;
      +			node.pos[1] += 5;
      +			this.graph.add( node );
      +			nodes.push( node );
      +		}
      +	}
      +
      +	//create links
      +	for(var i = 0; i < clipboard_info.links.length; ++i)
      +	{
      +		var link_info = clipboard_info.links[i];
      +		var origin_node = nodes[ link_info[0] ];
      +		var target_node = nodes[ link_info[2] ];
      +		origin_node.connect( link_info[1], target_node, link_info[3] );
      +	}
      +
      +	this.selectNodes( nodes );
      +}
      +
      +/**
      +* process a item drop event on top the canvas
      +* @method processDrop
      +**/
      +LGraphCanvas.prototype.processDrop = function(e)
      +{
      +	e.preventDefault();
      +	this.adjustMouseEvent(e);
      +
      +
      +	var pos = [e.canvasX,e.canvasY];
      +	var node = this.graph.getNodeOnPos(pos[0],pos[1]);
      +
      +	if(!node)
      +	{
      +		var r = null;
      +		if(this.onDropItem)
      +			r = this.onDropItem( event );
      +		if(!r)
      +			this.checkDropItem(e);
      +		return;
      +	}
      +
      +	if( node.onDropFile || node.onDropData )
      +	{
      +		var files = e.dataTransfer.files;
      +		if(files && files.length)
      +		{
      +			for(var i=0; i < files.length; i++)
      +			{
      +				var file = e.dataTransfer.files[0];
      +				var filename = file.name;
      +				var ext = LGraphCanvas.getFileExtension( filename );
      +				//console.log(file);
      +
      +				if(node.onDropFile)
      +					node.onDropFile(file);
      +
      +				if(node.onDropData)
      +				{
      +					//prepare reader
      +					var reader = new FileReader();
      +					reader.onload = function (event) {
      +						//console.log(event.target);
      +						var data = event.target.result;
      +						node.onDropData( data, filename, file );
      +					};
      +
      +					//read data
      +					var type = file.type.split("/")[0];
      +					if(type == "text" || type == "")
      +						reader.readAsText(file);
      +					else if (type == "image")
      +						reader.readAsDataURL(file);
      +					else
      +						reader.readAsArrayBuffer(file);
      +				}
      +			}
      +		}
      +	}
      +
      +	if(node.onDropItem)
      +	{
      +		if( node.onDropItem( event ) )
      +			return true;
      +	}
      +
      +	if(this.onDropItem)
      +		return this.onDropItem( event );
      +
      +	return false;
      +}
      +
      +//called if the graph doesnt have a default drop item behaviour
      +LGraphCanvas.prototype.checkDropItem = function(e)
      +{
      +	if(e.dataTransfer.files.length)
      +	{
      +		var file = e.dataTransfer.files[0];
      +		var ext = LGraphCanvas.getFileExtension( file.name ).toLowerCase();
      +		var nodetype = LiteGraph.node_types_by_file_extension[ext];
      +		if(nodetype)
      +		{
      +			var node = LiteGraph.createNode( nodetype.type );
      +			node.pos = [e.canvasX, e.canvasY];
      +			this.graph.add( node );
      +			if( node.onDropFile )
      +				node.onDropFile( file );
      +		}
      +	}
      +}
      +
      +
      +LGraphCanvas.prototype.processNodeDblClicked = function(n)
      +{
      +	if(this.onShowNodePanel)
      +		this.onShowNodePanel(n);
      +
      +	if(this.onNodeDblClicked)
      +		this.onNodeDblClicked(n);
      +
      +	this.setDirty(true);
      +}
      +
      +LGraphCanvas.prototype.processNodeSelected = function(node,e)
      +{
      +	this.selectNode( node, e && e.shiftKey );
      +	if(this.onNodeSelected)
      +		this.onNodeSelected(node);
      +}
      +
      +LGraphCanvas.prototype.processNodeDeselected = function(node)
      +{
      +	this.deselectNode(node);
      +	if(this.onNodeDeselected)
      +		this.onNodeDeselected(node);
      +}
      +
      +/**
      +* selects a given node (or adds it to the current selection)
      +* @method selectNode
      +**/
      +LGraphCanvas.prototype.selectNode = function( node, add_to_current_selection )
      +{
      +	if(node == null)
      +		this.deselectAllNodes();
      +	else
      +		this.selectNodes([node], add_to_current_selection );
      +}
      +
      +/**
      +* selects several nodes (or adds them to the current selection)
      +* @method selectNodes
      +**/
      +LGraphCanvas.prototype.selectNodes = function( nodes, add_to_current_selection )
      +{
      +	if(!add_to_current_selection)
      +		this.deselectAllNodes();
      +
      +	nodes = nodes || this.graph._nodes;
      +	for(var i = 0; i < nodes.length; ++i)
      +	{
      +		var node = nodes[i];
      +		if(node.selected)
      +			continue;
      +
      +		if( !node.selected && node.onSelected )
      +			node.onSelected();
      +		node.selected = true;
      +		this.selected_nodes[ node.id ] = node;
      +
      +		if(node.inputs)
      +			for(var i = 0; i < node.inputs.length; ++i)
      +				this.highlighted_links[ node.inputs[i].link ] = true;
      +		if(node.outputs)
      +			for(var i = 0; i < node.outputs.length; ++i)
      +			{
      +				var out = node.outputs[i];
      +				if( out.links )
      +					for(var j = 0; j < out.links.length; ++j)
      +						this.highlighted_links[ out.links[j] ] = true;
      +			}
      +
      +	}
      +
      +	this.setDirty(true);
      +}
      +
      +/**
      +* removes a node from the current selection
      +* @method deselectNode
      +**/
      +LGraphCanvas.prototype.deselectNode = function( node )
      +{
      +	if(!node.selected)
      +		return;
      +	if(node.onDeselected)
      +		node.onDeselected();
      +	node.selected = false;
      +
      +	//remove highlighted
      +	if(node.inputs)
      +		for(var i = 0; i < node.inputs.length; ++i)
      +			delete this.highlighted_links[ node.inputs[i].link ];
      +	if(node.outputs)
      +		for(var i = 0; i < node.outputs.length; ++i)
      +		{
      +			var out = node.outputs[i];
      +			if( out.links )
      +				for(var j = 0; j < out.links.length; ++j)
      +					delete this.highlighted_links[ out.links[j] ];
      +		}
      +}
      +
      +/**
      +* removes all nodes from the current selection
      +* @method deselectAllNodes
      +**/
      +LGraphCanvas.prototype.deselectAllNodes = function()
      +{
      +	if(!this.graph)
      +		return;
      +	var nodes = this.graph._nodes;
      +	for(var i = 0, l = nodes.length; i < l; ++i)
      +	{
      +		var node = nodes[i];
      +		if(!node.selected)
      +			continue;
      +		if(node.onDeselected)
      +			node.onDeselected();
      +		node.selected = false;
      +	}
      +	this.selected_nodes = {};
      +	this.highlighted_links = {};
      +	this.setDirty(true);
      +}
      +
      +/**
      +* deletes all nodes in the current selection from the graph
      +* @method deleteSelectedNodes
      +**/
      +LGraphCanvas.prototype.deleteSelectedNodes = function()
      +{
      +	for(var i in this.selected_nodes)
      +	{
      +		var m = this.selected_nodes[i];
      +		//if(m == this.node_in_panel) this.showNodePanel(null);
      +		this.graph.remove(m);
      +	}
      +	this.selected_nodes = {};
      +	this.highlighted_links = {};
      +	this.setDirty(true);
      +}
      +
      +/**
      +* centers the camera on a given node
      +* @method centerOnNode
      +**/
      +LGraphCanvas.prototype.centerOnNode = function(node)
      +{
      +	this.offset[0] = -node.pos[0] - node.size[0] * 0.5 + (this.canvas.width * 0.5 / this.scale);
      +	this.offset[1] = -node.pos[1] - node.size[1] * 0.5 + (this.canvas.height * 0.5 / this.scale);
      +	this.setDirty(true,true);
      +}
      +
      +/**
      +* adds some useful properties to a mouse event, like the position in graph coordinates
      +* @method adjustMouseEvent
      +**/
      +LGraphCanvas.prototype.adjustMouseEvent = function(e)
      +{
      +	if(this.canvas)
      +	{
      +		var b = this.canvas.getBoundingClientRect();
      +		e.localX = e.pageX - b.left;
      +		e.localY = e.pageY - b.top;
      +	}
      +	else
      +	{
      +		e.localX = e.pageX;
      +		e.localY = e.pageY;
      +	}
      +
      +	e.deltaX = e.localX - this.last_mouse_position[0];
      +	e.deltaY = e.localY - this.last_mouse_position[1];
      +
      +	this.last_mouse_position[0] = e.localX;
      +	this.last_mouse_position[1] = e.localY;
      +
      +	e.canvasX = e.localX / this.scale - this.offset[0];
      +	e.canvasY = e.localY / this.scale - this.offset[1];
      +}
      +
      +/**
      +* changes the zoom level of the graph (default is 1), you can pass also a place used to pivot the zoom
      +* @method setZoom
      +**/
      +LGraphCanvas.prototype.setZoom = function(value, zooming_center)
      +{
      +	if(!zooming_center && this.canvas)
      +		zooming_center = [this.canvas.width * 0.5,this.canvas.height * 0.5];
      +
      +	var center = this.convertOffsetToCanvas( zooming_center );
      +
      +	this.scale = value;
      +
      +	if(this.scale > this.max_zoom)
      +		this.scale = this.max_zoom;
      +	else if(this.scale < this.min_zoom)
      +		this.scale = this.min_zoom;
      +
      +	var new_center = this.convertOffsetToCanvas( zooming_center );
      +	var delta_offset = [new_center[0] - center[0], new_center[1] - center[1]];
      +
      +	this.offset[0] += delta_offset[0];
      +	this.offset[1] += delta_offset[1];
      +
      +	this.dirty_canvas = true;
      +	this.dirty_bgcanvas = true;
      +}
      +
      +/**
      +* converts a coordinate in canvas2D space to graphcanvas space (NAME IS CONFUSION, SHOULD BE THE OTHER WAY AROUND)
      +* @method convertOffsetToCanvas
      +**/
      +LGraphCanvas.prototype.convertOffsetToCanvas = function( pos, out )
      +{
      +	out = out || [];
      +	out[0] = pos[0] / this.scale - this.offset[0];
      +	out[1] = pos[1] / this.scale - this.offset[1];
      +	return out;
      +}
      +
      +/**
      +* converts a coordinate in graphcanvas space to canvas2D space (NAME IS CONFUSION, SHOULD BE THE OTHER WAY AROUND)
      +* @method convertCanvasToOffset
      +**/
      +LGraphCanvas.prototype.convertCanvasToOffset = function( pos, out )
      +{
      +	out = out || [];
      +	out[0] = (pos[0] + this.offset[0]) * this.scale;
      +	out[1] = (pos[1] + this.offset[1]) * this.scale;
      +	return out;
      +}
      +
      +LGraphCanvas.prototype.convertEventToCanvas = function(e)
      +{
      +	var rect = this.canvas.getBoundingClientRect();
      +	return this.convertOffsetToCanvas([e.pageX - rect.left,e.pageY - rect.top]);
      +}
      +
      +/**
      +* brings a node to front (above all other nodes)
      +* @method bringToFront
      +**/
      +LGraphCanvas.prototype.bringToFront = function(node)
      +{
      +	var i = this.graph._nodes.indexOf(node);
      +	if(i == -1) return;
      +
      +	this.graph._nodes.splice(i,1);
      +	this.graph._nodes.push(node);
      +}
      +
      +/**
      +* sends a node to the back (below all other nodes)
      +* @method sendToBack
      +**/
      +LGraphCanvas.prototype.sendToBack = function(node)
      +{
      +	var i = this.graph._nodes.indexOf(node);
      +	if(i == -1) return;
      +
      +	this.graph._nodes.splice(i,1);
      +	this.graph._nodes.unshift(node);
      +}
      +
      +/* Interaction */
      +
      +
      +
      +/* LGraphCanvas render */
      +var temp = new Float32Array(4);
      +
      +/**
      +* checks which nodes are visible (inside the camera area)
      +* @method computeVisibleNodes
      +**/
      +LGraphCanvas.prototype.computeVisibleNodes = function( nodes, out )
      +{
      +	var visible_nodes = out || [];
      +	visible_nodes.length = 0;
      +	nodes = nodes || this.graph._nodes;
      +	for(var i = 0, l = nodes.length; i < l; ++i)
      +	{
      +		var n = nodes[i];
      +
      +		//skip rendering nodes in live mode
      +		if(this.live_mode && !n.onDrawBackground && !n.onDrawForeground)
      +			continue;
      +
      +		if(!overlapBounding( this.visible_area, n.getBounding( temp ) ))
      +			continue; //out of the visible area
      +
      +		visible_nodes.push(n);
      +	}
      +	return visible_nodes;
      +}
      +
      +/**
      +* renders the whole canvas content, by rendering in two separated canvas, one containing the background grid and the connections, and one containing the nodes)
      +* @method draw
      +**/
      +LGraphCanvas.prototype.draw = function(force_canvas, force_bgcanvas)
      +{
      +	if(!this.canvas)
      +		return;
      +
      +	//fps counting
      +	var now = LiteGraph.getTime();
      +	this.render_time = (now - this.last_draw_time)*0.001;
      +	this.last_draw_time = now;
      +
      +	if(this.graph)
      +	{
      +		var start = [-this.offset[0], -this.offset[1] ];
      +		var end = [start[0] + this.canvas.width / this.scale, start[1] + this.canvas.height / this.scale];
      +		this.visible_area = new Float32Array([ start[0], start[1], end[0] - start[0], end[1] - start[1] ]);
      +	}
      +
      +	if(this.dirty_bgcanvas || force_bgcanvas || this.always_render_background || (this.graph && this.graph._last_trigger_time && (now - this.graph._last_trigger_time) < 1000) )
      +		this.drawBackCanvas();
      +
      +	if(this.dirty_canvas || force_canvas)
      +		this.drawFrontCanvas();
      +
      +	this.fps = this.render_time ? (1.0 / this.render_time) : 0;
      +	this.frame += 1;
      +}
      +
      +/**
      +* draws the front canvas (the one containing all the nodes)
      +* @method drawFrontCanvas
      +**/
      +LGraphCanvas.prototype.drawFrontCanvas = function()
      +{
      +	this.dirty_canvas = false;
      +
      +	if(!this.ctx)
      +		this.ctx = this.bgcanvas.getContext("2d");
      +	var ctx = this.ctx;
      +	if(!ctx) //maybe is using webgl...
      +		return;
      +
      +	if(ctx.start2D)
      +		ctx.start2D();
      +
      +	var canvas = this.canvas;
      +
      +	//reset in case of error
      +	ctx.restore();
      +	ctx.setTransform(1, 0, 0, 1, 0, 0);
      +
      +	//clip dirty area if there is one, otherwise work in full canvas
      +	if(this.dirty_area)
      +	{
      +		ctx.save();
      +		ctx.beginPath();
      +		ctx.rect(this.dirty_area[0],this.dirty_area[1],this.dirty_area[2],this.dirty_area[3]);
      +		ctx.clip();
      +	}
      +
      +	//clear
      +	//canvas.width = canvas.width;
      +	if(this.clear_background)
      +		ctx.clearRect(0,0,canvas.width, canvas.height);
      +
      +	//draw bg canvas
      +	if(this.bgcanvas == this.canvas)
      +		this.drawBackCanvas();
      +	else
      +		ctx.drawImage(this.bgcanvas,0,0);
      +
      +	//rendering
      +	if(this.onRender)
      +		this.onRender(canvas, ctx);
      +
      +	//info widget
      +	if(this.show_info)
      +		this.renderInfo(ctx);
      +
      +	if(this.graph)
      +	{
      +		//apply transformations
      +		ctx.save();
      +		ctx.scale(this.scale,this.scale);
      +		ctx.translate( this.offset[0],this.offset[1] );
      +
      +		//draw nodes
      +		var drawn_nodes = 0;
      +		var visible_nodes = this.computeVisibleNodes( null, this.visible_nodes );
      +
      +		for (var i = 0; i < visible_nodes.length; ++i)
      +		{
      +			var node = visible_nodes[i];
      +
      +			//transform coords system
      +			ctx.save();
      +			ctx.translate( node.pos[0], node.pos[1] );
      +
      +			//Draw
      +			this.drawNode( node, ctx );
      +			drawn_nodes += 1;
      +
      +			//Restore
      +			ctx.restore();
      +		}
      +
      +		//connections ontop?
      +		if(this.graph.config.links_ontop)
      +			if(!this.live_mode)
      +				this.drawConnections(ctx);
      +
      +		//current connection
      +		if(this.connecting_pos != null)
      +		{
      +			ctx.lineWidth = this.connections_width;
      +			var link_color = null;
      +			switch( this.connecting_output.type )
      +			{
      +				case LiteGraph.EVENT: link_color = "#F85"; break;
      +				default:
      +					link_color = "#AFA";
      +			}
      +			//the connection being dragged by the mouse
      +			this.renderLink( ctx, this.connecting_pos, [this.canvas_mouse[0],this.canvas_mouse[1]], null, false, null, link_color );
      +
      +			ctx.beginPath();
      +				if( this.connecting_output.type === LiteGraph.EVENT || this.connecting_output.shape === LiteGraph.BOX_SHAPE )
      +					ctx.rect( (this.connecting_pos[0] - 6) + 0.5, (this.connecting_pos[1] - 5) + 0.5,14,10);
      +				else
      +					ctx.arc( this.connecting_pos[0], this.connecting_pos[1],4,0,Math.PI*2);
      +			ctx.fill();
      +
      +			ctx.fillStyle = "#ffcc00";
      +			if(this._highlight_input)
      +			{
      +				ctx.beginPath();
      +					ctx.arc( this._highlight_input[0], this._highlight_input[1],6,0,Math.PI*2);
      +				ctx.fill();
      +			}
      +		}
      +
      +		if( this.dragging_rectangle )
      +		{
      +			ctx.strokeStyle = "#FFF";
      +			ctx.strokeRect( this.dragging_rectangle[0], this.dragging_rectangle[1], this.dragging_rectangle[2], this.dragging_rectangle[3] );
      +		}
      +
      +
      +		ctx.restore();
      +	}
      +
      +	if(this.dirty_area)
      +	{
      +		ctx.restore();
      +		//this.dirty_area = null;
      +	}
      +
      +	if(ctx.finish2D) //this is a function I use in webgl renderer
      +		ctx.finish2D();
      +}
      +
      +/**
      +* 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;
      +
      +	ctx.save();
      +	ctx.translate( x, y );
      +
      +	ctx.font = "10px Arial";
      +	ctx.fillStyle = "#888";
      +	if(this.graph)
      +	{
      +		ctx.fillText( "T: " + this.graph.globaltime.toFixed(2)+"s",5,13*1 );
      +		ctx.fillText( "I: " + this.graph.iteration,5,13*2 );
      +		ctx.fillText( "V: " + this.graph._version,5,13*3 );
      +		ctx.fillText( "FPS:" + this.fps.toFixed(2),5,13*4 );
      +	}
      +	else
      +		ctx.fillText( "No graph selected",5,13*1 );
      +	ctx.restore();
      +}
      +
      +/**
      +* draws the back canvas (the one containing the background and the connections)
      +* @method drawBackCanvas
      +**/
      +LGraphCanvas.prototype.drawBackCanvas = function()
      +{
      +	var canvas = this.bgcanvas;
      +	if(canvas.width != this.canvas.width ||
      +		canvas.height != this.canvas.height)
      +	{
      +		canvas.width = this.canvas.width;
      +		canvas.height = this.canvas.height;
      +	}
      +
      +	if(!this.bgctx)
      +		this.bgctx = this.bgcanvas.getContext("2d");
      +	var ctx = this.bgctx;
      +	if(ctx.start)
      +		ctx.start();
      +
      +	//clear
      +	if(this.clear_background)
      +		ctx.clearRect(0,0,canvas.width, canvas.height);
      +
      +	if(this._graph_stack && this._graph_stack.length)
      +	{
      +		ctx.strokeStyle = this._graph_stack[ this._graph_stack.length - 1].bgcolor;
      +		ctx.lineWidth = 10;
      +		ctx.strokeRect(1,1,canvas.width-2,canvas.height-2);
      +		ctx.lineWidth = 1;
      +	}
      +
      +	var bg_already_painted = false;
      +	if(this.onRenderBackground)
      +		bg_already_painted = this.onRenderBackground( canvas, ctx );
      +
      +	//reset in case of error
      +	ctx.restore();
      +	ctx.setTransform(1, 0, 0, 1, 0, 0);
      +
      +	if(this.graph)
      +	{
      +		//apply transformations
      +		ctx.save();
      +		ctx.scale(this.scale,this.scale);
      +		ctx.translate(this.offset[0],this.offset[1]);
      +
      +		//render BG
      +		if(this.background_image && this.scale > 0.5 && !bg_already_painted)
      +		{
      +			if (this.zoom_modify_alpha)
      +				ctx.globalAlpha = (1.0 - 0.5 / this.scale) * this.editor_alpha;
      +			else
      +				ctx.globalAlpha = this.editor_alpha;
      +			ctx.imageSmoothingEnabled = ctx.mozImageSmoothingEnabled = ctx.imageSmoothingEnabled = false;
      +			if(!this._bg_img || this._bg_img.name != this.background_image)
      +			{
      +				this._bg_img = new Image();
      +				this._bg_img.name = this.background_image;
      +				this._bg_img.src = this.background_image;
      +				var that = this;
      +				this._bg_img.onload = function() {
      +					that.draw(true,true);
      +				}
      +			}
      +
      +			var pattern = null;
      +			if(this._pattern == null && this._bg_img.width > 0)
      +			{
      +				pattern = ctx.createPattern( this._bg_img, 'repeat' );
      +				this._pattern_img = this._bg_img;
      +				this._pattern = pattern;
      +			}
      +			else
      +				pattern = this._pattern;
      +			if(pattern)
      +			{
      +				ctx.fillStyle = pattern;
      +				ctx.fillRect(this.visible_area[0],this.visible_area[1],this.visible_area[2],this.visible_area[3]);
      +				ctx.fillStyle = "transparent";
      +			}
      +
      +			ctx.globalAlpha = 1.0;
      +			ctx.imageSmoothingEnabled = ctx.mozImageSmoothingEnabled = ctx.imageSmoothingEnabled = true;
      +		}
      +
      +		//groups
      +		if(this.graph._groups.length && !this.live_mode)
      +			this.drawGroups(canvas, ctx);
      +
      +		if(this.onBackgroundRender)
      +			this.onBackgroundRender(canvas, ctx);
      +
      +		//DEBUG: show clipping area
      +		//ctx.fillStyle = "red";
      +		//ctx.fillRect( this.visible_area[0] + 10, this.visible_area[1] + 10, this.visible_area[2] - 20, this.visible_area[3] - 20);
      +
      +		//bg
      +		if (this.render_canvas_border) {
      +			ctx.strokeStyle = "#235";
      +			ctx.strokeRect(0,0,canvas.width,canvas.height);
      +		}
      +
      +		if(this.render_connections_shadows)
      +		{
      +			ctx.shadowColor = "#000";
      +			ctx.shadowOffsetX = 0;
      +			ctx.shadowOffsetY = 0;
      +			ctx.shadowBlur = 6;
      +		}
      +		else
      +			ctx.shadowColor = "rgba(0,0,0,0)";
      +
      +		//draw connections
      +		if(!this.live_mode)
      +			this.drawConnections(ctx);
      +
      +		ctx.shadowColor = "rgba(0,0,0,0)";
      +
      +		//restore state
      +		ctx.restore();
      +	}
      +
      +	if(ctx.finish)
      +		ctx.finish();
      +
      +	this.dirty_bgcanvas = false;
      +	this.dirty_canvas = true; //to force to repaint the front canvas with the bgcanvas
      +}
      +
      +var temp_vec2 = new Float32Array(2);
      +
      +/**
      +* draws the given node inside the canvas
      +* @method drawNode
      +**/
      +LGraphCanvas.prototype.drawNode = function(node, ctx )
      +{
      +	var glow = false;
      +	this.current_node = node;
      +
      +	var color = node.color || node.constructor.color || LiteGraph.NODE_DEFAULT_COLOR;
      +	var bgcolor = node.bgcolor || node.constructor.bgcolor || LiteGraph.NODE_DEFAULT_BGCOLOR;
      +
      +	//shadow and glow
      +	if (node.mouseOver)
      +		glow = true;
      +
      +	if(node.selected)
      +	{
      +		/*
      +		ctx.shadowColor = "#EEEEFF";//glow ? "#AAF" : "#000";
      +		ctx.shadowOffsetX = 0;
      +		ctx.shadowOffsetY = 0;
      +		ctx.shadowBlur = 1;
      +		*/
      +	}
      +	else if(this.render_shadows)
      +	{
      +		ctx.shadowColor = "rgba(0,0,0,0.5)";
      +		ctx.shadowOffsetX = 2;
      +		ctx.shadowOffsetY = 2;
      +		ctx.shadowBlur = 3;
      +	}
      +	else
      +		ctx.shadowColor = "transparent";
      +
      +	//only render if it forces it to do it
      +	if(this.live_mode)
      +	{
      +		if(!node.flags.collapsed)
      +		{
      +			ctx.shadowColor = "transparent";
      +			if(node.onDrawForeground)
      +				node.onDrawForeground(ctx, this);
      +		}
      +
      +		return;
      +	}
      +
      +	//custom draw collapsed method
      +	if(node.flags.collapsed && node.onDrawCollaped && node.onDrawCollapsed(ctx, this) == true)
      +		return;
      +
      +	var editor_alpha = this.editor_alpha;
      +	ctx.globalAlpha = editor_alpha;
      +
      +	//clip if required (mask)
      +	var shape = node._shape || LiteGraph.BOX_SHAPE;
      +	var size = temp_vec2;
      +	temp_vec2.set( node.size );
      +	if( node.flags.collapsed )
      +	{
      +		ctx.font = this.inner_text_font;
      +		var title = node.getTitle ? node.getTitle() : node.title;
      +		node._collapsed_width = Math.min( node.size[0], ctx.measureText(title).width + 40 );//LiteGraph.NODE_COLLAPSED_WIDTH;
      +		size[0] = node._collapsed_width;
      +		size[1] = 0;
      +	}
      +	
      +	if( node.flags.clip_area ) //Start clipping
      +	{
      +		ctx.save();
      +		ctx.beginPath();
      +		if(shape == LiteGraph.BOX_SHAPE)
      +			ctx.rect(0,0,size[0], size[1]);
      +		else if (shape == LiteGraph.ROUND_SHAPE)
      +			ctx.roundRect(0,0,size[0], size[1],10);
      +		else if (shape == LiteGraph.CIRCLE_SHAPE)
      +			ctx.arc(size[0] * 0.5, size[1] * 0.5, size[0] * 0.5, 0, Math.PI*2);
      +		ctx.clip();
      +	}
      +
      +	//draw shape
      +	this.drawNodeShape( node, ctx, size, color, bgcolor, node.selected, node.mouseOver );
      +	ctx.shadowColor = "transparent";
      +
      +	//connection slots
      +	ctx.textAlign = "left";
      +	ctx.font = this.inner_text_font;
      +
      +	var render_text = this.scale > 0.6;
      +
      +	var out_slot = this.connecting_output;
      +	ctx.lineWidth = 1;
      +
      +	var max_y = 0;
      +
      +	//render inputs and outputs
      +	if(!node.flags.collapsed)
      +	{
      +		//input connection slots
      +		if(node.inputs)
      +			for(var i = 0; i < node.inputs.length; i++)
      +			{
      +				var slot = node.inputs[i];
      +
      +				ctx.globalAlpha = editor_alpha;
      +				//change opacity of incompatible slots when dragging a connection
      +				if ( this.connecting_node && LiteGraph.isValidConnection( slot.type && out_slot.type ) )
      +					ctx.globalAlpha = 0.4 * editor_alpha;
      +
      +				ctx.fillStyle = slot.link != null ? (slot.colorOn || this.default_connection_color.input_on) : (slot.colorOff || this.default_connection_color.input_off);
      +
      +				var pos = node.getConnectionPos( true, i );
      +				pos[0] -= node.pos[0];
      +				pos[1] -= node.pos[1];
      +				if( max_y < pos[1] + LiteGraph.NODE_SLOT_HEIGHT*0.5 )
      +					max_y = pos[1] + LiteGraph.NODE_SLOT_HEIGHT*0.5;
      +
      +				ctx.beginPath();
      +
      +				if (slot.type === LiteGraph.EVENT || slot.shape === LiteGraph.BOX_SHAPE) {
      +                    ctx.rect((pos[0] - 6) + 0.5, (pos[1] - 5) + 0.5, 14, 10);
      +                } else if (slot.shape === LiteGraph.ARROW_SHAPE) {
      +                    ctx.moveTo(pos[0] + 8, pos[1] + 0.5);
      +                    ctx.lineTo(pos[0] - 4, (pos[1] + 6) + 0.5);
      +                    ctx.lineTo(pos[0] - 4, (pos[1] - 6) + 0.5);
      +                    ctx.closePath();
      +                } else {
      +                    ctx.arc(pos[0], pos[1], 4, 0, Math.PI * 2);
      +                }
      +
      +				ctx.fill();
      +
      +				//render name
      +				if(render_text)
      +				{
      +					var text = slot.label != null ? slot.label : slot.name;
      +					if(text)
      +					{
      +						ctx.fillStyle = LiteGraph.NODE_TEXT_COLOR;
      +						ctx.fillText(text,pos[0] + 10,pos[1] + 5);
      +					}
      +				}
      +			}
      +
      +		//output connection slots
      +		if(this.connecting_node)
      +			ctx.globalAlpha = 0.4 * editor_alpha;
      +
      +		ctx.textAlign = "right";
      +		ctx.strokeStyle = "black";
      +		if(node.outputs)
      +			for(var i = 0; i < node.outputs.length; i++)
      +			{
      +				var slot = node.outputs[i];
      +
      +				var pos = node.getConnectionPos(false,i);
      +				pos[0] -= node.pos[0];
      +				pos[1] -= node.pos[1];
      +				if( max_y < pos[1] + LiteGraph.NODE_SLOT_HEIGHT*0.5)
      +					max_y = pos[1] + LiteGraph.NODE_SLOT_HEIGHT*0.5;
      +
      +				ctx.fillStyle = slot.links && slot.links.length ? (slot.colorOn || this.default_connection_color.output_on) : (slot.colorOff || this.default_connection_color.output_off);
      +				ctx.beginPath();
      +				//ctx.rect( node.size[0] - 14,i*14,10,10);
      +
      +				if (slot.type === LiteGraph.EVENT || slot.shape === LiteGraph.BOX_SHAPE) {
      +					ctx.rect((pos[0] - 6) + 0.5,(pos[1] - 5) + 0.5,14,10);
      +                } else if (slot.shape === LiteGraph.ARROW_SHAPE) {
      +                    ctx.moveTo(pos[0] + 8, pos[1] + 0.5);
      +                    ctx.lineTo(pos[0] - 4, (pos[1] + 6) + 0.5);
      +                    ctx.lineTo(pos[0] - 4, (pos[1] - 6) + 0.5);
      +                    ctx.closePath();
      +                } else {
      +                    ctx.arc(pos[0], pos[1], 4, 0, Math.PI * 2);
      +                }
      +
      +				//trigger
      +				//if(slot.node_id != null && slot.slot == -1)
      +				//	ctx.fillStyle = "#F85";
      +
      +				//if(slot.links != null && slot.links.length)
      +				ctx.fill();
      +				ctx.stroke();
      +
      +				//render output name
      +				if(render_text)
      +				{
      +					var text = slot.label != null ? slot.label : slot.name;
      +					if(text)
      +					{
      +						ctx.fillStyle = LiteGraph.NODE_TEXT_COLOR;
      +						ctx.fillText(text, pos[0] - 10,pos[1] + 5);
      +					}
      +				}
      +			}
      +
      +		ctx.textAlign = "left";
      +		ctx.globalAlpha = 1;
      +
      +		if(node.widgets)
      +			this.drawNodeWidgets( node, max_y, ctx, (this.node_widget && this.node_widget[0] == node) ? this.node_widget[1] : null );
      +
      +		//draw foreground
      +		if(node.onDrawForeground)
      +		{
      +			//immediate gui stuff
      +			if( node.gui_rects )
      +				node.gui_rects.length = 0;
      +			node.onDrawForeground( ctx, this );
      +		}
      +	}
      +	else //if collapsed
      +	{
      +		if(node.inputs)
      +		{
      +			for(var i = 0; i < node.inputs.length; i++)
      +			{
      +				var slot = node.inputs[i];
      +				if( slot.link == null )
      +					continue;
      +				ctx.fillStyle = slot.colorOn || this.default_connection_color.input_on;
      +				ctx.beginPath();
      +				if ( slot.type === LiteGraph.EVENT || slot.shape === LiteGraph.BOX_SHAPE) {
      +					ctx.rect(0.5, 4 - LiteGraph.NODE_TITLE_HEIGHT + 0.5,14,LiteGraph.NODE_TITLE_HEIGHT - 8);
      +                } else if (slot.shape === LiteGraph.ARROW_SHAPE) {
      +                    ctx.moveTo(8, LiteGraph.NODE_TITLE_HEIGHT * -0.5);
      +                    ctx.lineTo(-4, LiteGraph.NODE_TITLE_HEIGHT * -0.8);
      +                    ctx.lineTo(-4, LiteGraph.NODE_TITLE_HEIGHT * -0.2);
      +                    ctx.closePath();
      +                } else {
      +                    ctx.arc(0, LiteGraph.NODE_TITLE_HEIGHT * -0.5, 4, 0, Math.PI * 2);
      +                }
      +				ctx.fill();
      +				break;
      +			}
      +		}
      +
      +		if(node.outputs)
      +		{
      +			for(var i = 0; i < node.outputs.length; i++)
      +			{
      +				var slot = node.outputs[i];
      +				if(!slot.links || !slot.links.length)
      +					continue;
      +				ctx.fillStyle = slot.colorOn || this.default_connection_color.output_on;
      +				ctx.strokeStyle = "black";
      +				ctx.beginPath();
      +				if (slot.type === LiteGraph.EVENT || slot.shape === LiteGraph.BOX_SHAPE) {
      +					ctx.rect( node._collapsed_width - 4 + 0.5, 4 - LiteGraph.NODE_TITLE_HEIGHT + 0.5,14,LiteGraph.NODE_TITLE_HEIGHT - 8);
      +                } else if (slot.shape === LiteGraph.ARROW_SHAPE) {
      +                    ctx.moveTo(node._collapsed_width + 6, LiteGraph.NODE_TITLE_HEIGHT * -0.5);
      +                    ctx.lineTo(node._collapsed_width - 6, LiteGraph.NODE_TITLE_HEIGHT * -0.8);
      +                    ctx.lineTo(node._collapsed_width - 6, LiteGraph.NODE_TITLE_HEIGHT * -0.2);
      +                    ctx.closePath();
      +                } else {
      +                    ctx.arc(node._collapsed_width, LiteGraph.NODE_TITLE_HEIGHT * -0.5, 4, 0, Math.PI * 2);
      +                }
      +				ctx.fill();
      +				ctx.stroke();
      +			}
      +		}
      +		
      +	}
      +
      +	if(node.flags.clip_area)
      +		ctx.restore();
      +
      +	ctx.globalAlpha = 1.0;
      +}
      +
      +/**
      +* draws the shape of the given node in the canvas
      +* @method drawNodeShape
      +**/
      +LGraphCanvas.prototype.drawNodeShape = function( node, ctx, size, fgcolor, bgcolor, selected, mouse_over )
      +{
      +	//bg rect
      +	ctx.strokeStyle = fgcolor;
      +	ctx.fillStyle = bgcolor;
      +
      +	var title_height = LiteGraph.NODE_TITLE_HEIGHT;
      +
      +	//render node area depending on shape
      +	var shape = node._shape || node.constructor.shape || LiteGraph.BOX_SHAPE;
      +	var title_mode = node.constructor.title_mode;
      +
      +	var render_title = true;
      +	if( title_mode == LiteGraph.TRANSPARENT_TITLE )
      +		render_title = false;
      +	else if( title_mode == LiteGraph.AUTOHIDE_TITLE && mouse_over)
      +		render_title = true;
      +
      +	var areax = 0;
      +	var areay = render_title ? -title_height : 0;
      +	var areaw = size[0]+1;
      +	var areah = render_title ? size[1] + title_height : size[1];
      +
      +	if(!node.flags.collapsed)
      +	{
      +		if(shape == LiteGraph.BOX_SHAPE || this.scale < 0.5)
      +		{
      +			ctx.beginPath();
      +			ctx.rect( areax, areay, areaw, areah );
      +			ctx.fill();
      +		}
      +		else if (shape == LiteGraph.ROUND_SHAPE || shape == LiteGraph.CARD_SHAPE)
      +		{
      +			ctx.beginPath();
      +			ctx.roundRect( areax, areay, areaw, areah, this.round_radius, shape == LiteGraph.CARD_SHAPE ? 0 : this.round_radius);
      +			ctx.fill();
      +		}
      +		else if (shape == LiteGraph.CIRCLE_SHAPE)
      +		{
      +			ctx.beginPath();
      +			ctx.arc(size[0] * 0.5, size[1] * 0.5, size[0] * 0.5, 0, Math.PI*2);
      +			ctx.fill();
      +		}
      +	}
      +	ctx.shadowColor = "transparent";
      +
      +	//image
      +	if (node.bgImage && node.bgImage.width)
      +		ctx.drawImage( node.bgImage, (size[0] - node.bgImage.width) * 0.5 , (size[1] - node.bgImage.height) * 0.5);
      +
      +	if(node.bgImageUrl && !node.bgImage)
      +		node.bgImage = node.loadImage(node.bgImageUrl);
      +
      +	if( node.onDrawBackground )
      +	{
      +		//immediate gui stuff
      +		if( node.gui_rects )
      +			node.gui_rects.length = 0;
      +		node.onDrawBackground( ctx, this );
      +	}
      +
      +	//title bg (remember, it is rendered ABOVE the node)
      +	if(render_title || title_mode == LiteGraph.TRANSPARENT_TITLE )
      +	{
      +		//title bar
      +		if(title_mode != LiteGraph.TRANSPARENT_TITLE) //!node.flags.collapsed)
      +		{
      +			//* gradient test
      +			if(this.use_gradients)
      +			{
      +				var grad = LGraphCanvas.gradients[ fgcolor ];
      +				if(!grad)
      +				{
      +					grad = LGraphCanvas.gradients[ fgcolor ] = ctx.createLinearGradient(0,0,400,0);
      +					grad.addColorStop(0, fgcolor);
      +					grad.addColorStop(1, "#000");
      +				}
      +				ctx.fillStyle = grad;
      +			}
      +			else
      +				ctx.fillStyle = fgcolor;
      +
      +			var old_alpha = ctx.globalAlpha;
      +			//ctx.globalAlpha = 0.5 * old_alpha;
      +			ctx.beginPath();
      +			if(shape == LiteGraph.BOX_SHAPE || this.scale < 0.5)
      +			{
      +				ctx.rect(0, -title_height, size[0]+1, title_height);
      +				ctx.fill()
      +				//ctx.stroke();
      +			}
      +			else if (shape == LiteGraph.ROUND_SHAPE || shape == LiteGraph.CARD_SHAPE)
      +			{
      +				ctx.roundRect(0,-title_height,size[0]+1, title_height, this.round_radius, node.flags.collapsed ? this.round_radius : 0);
      +				ctx.fill();
      +			}
      +
      +			/*
      +			else if (shape == LiteGraph.CIRCLE_SHAPE)
      +			{
      +				ctx.beginPath();
      +				ctx.arc(title_height *0.5, title_height * -0.5, (title_height - 6) *0.5,0,Math.PI*2);
      +				ctx.fill();
      +			}
      +			*/
      +		}
      +
      +		//title box
      +		ctx.fillStyle = node.boxcolor || LiteGraph.NODE_DEFAULT_BOXCOLOR;
      +		ctx.beginPath();
      +		if (shape == LiteGraph.ROUND_SHAPE || shape == LiteGraph.CIRCLE_SHAPE || shape == LiteGraph.CARD_SHAPE)
      +			ctx.arc(title_height *0.5, title_height * -0.5, (title_height - 8) *0.5,0,Math.PI*2);
      +		else
      +			ctx.rect(4,-title_height + 4,title_height - 8,title_height - 8);
      +		ctx.fill();
      +		ctx.globalAlpha = old_alpha;
      +
      +		//title text
      +		if( this.scale > 0.5 )
      +		{
      +			ctx.font = this.title_text_font;
      +			var title = node.getTitle();
      +			if(title)
      +			{
      +				if(selected)
      +					ctx.fillStyle = "white";
      +				else
      +					ctx.fillStyle = node.constructor.title_text_color || this.node_title_color;
      +				if( node.flags.collapsed )
      +				{
      +					ctx.textAlign =  "center";
      +					var measure = ctx.measureText(title);
      +					ctx.fillText( title, title_height + measure.width * 0.5, -title_height * 0.2 );
      +					ctx.textAlign =  "left";
      +				}
      +				else
      +				{
      +					ctx.textAlign =  "left";
      +					ctx.fillText( title, title_height, -title_height * 0.2 );
      +				}
      +			}
      +		}
      +	}
      +
      +	//render selection marker
      +	if(selected)
      +	{
      +		if( title_mode == LiteGraph.TRANSPARENT_TITLE )
      +		{
      +			areay -= title_height;
      +			areah += title_height;
      +		}
      +		ctx.lineWidth = 1;
      +		ctx.beginPath();
      +		if(shape == LiteGraph.BOX_SHAPE)
      +			ctx.rect(-6 + areax,-6 + areay, 12 + areaw, 12 + areah );
      +		else if (shape == LiteGraph.ROUND_SHAPE || (shape == LiteGraph.CARD_SHAPE && node.flags.collapsed) )
      +			ctx.roundRect(-6 + areax,-6 + areay, 12 + areaw, 12 + areah , this.round_radius * 2);
      +		else if (shape == LiteGraph.CARD_SHAPE)
      +			ctx.roundRect(-6 + areax,-6 + areay, 12 + areaw, 12 + areah , this.round_radius * 2, 2);
      +		else if (shape == LiteGraph.CIRCLE_SHAPE)
      +			ctx.arc(size[0] * 0.5, size[1] * 0.5, size[0] * 0.5 + 6, 0, Math.PI*2);
      +		ctx.strokeStyle = "#DDD";
      +		ctx.stroke();
      +		ctx.strokeStyle = fgcolor;
      +	}
      +}
      +
      +/**
      +* draws every connection visible in the canvas
      +* OPTIMIZE THIS: precatch connections position instead of recomputing them every time
      +* @method drawConnections
      +**/
      +LGraphCanvas.prototype.drawConnections = function(ctx)
      +{
      +	var now = LiteGraph.getTime();
      +
      +	//draw connections
      +	ctx.lineWidth = this.connections_width;
      +
      +	ctx.fillStyle = "#AAA";
      +	ctx.strokeStyle = "#AAA";
      +	ctx.globalAlpha = this.editor_alpha;
      +	//for every node
      +	for (var n = 0, l = this.graph._nodes.length; n < l; ++n)
      +	{
      +		var node = this.graph._nodes[n];
      +		//for every input (we render just inputs because it is easier as every slot can only have one input)
      +		if(node.inputs && node.inputs.length)
      +			for(var i = 0; i < node.inputs.length; ++i)
      +			{
      +				var input = node.inputs[i];
      +				if(!input || input.link == null)
      +					continue;
      +				var link_id = input.link;
      +				var link = this.graph.links[ link_id ];
      +				if(!link)
      +					continue;
      +
      +				var start_node = this.graph.getNodeById( link.origin_id );
      +				if(start_node == null) continue;
      +				var start_node_slot = link.origin_slot;
      +				var start_node_slotpos = null;
      +
      +				if(start_node_slot == -1)
      +					start_node_slotpos = [start_node.pos[0] + 10, start_node.pos[1] + 10];
      +				else
      +					start_node_slotpos = start_node.getConnectionPos(false, start_node_slot);
      +
      +				this.renderLink( ctx, start_node_slotpos, node.getConnectionPos(true,i), link );
      +
      +				//event triggered rendered on top
      +				if(link && link._last_time && (now - link._last_time) < 1000 )
      +				{
      +					var f = 2.0 - (now - link._last_time) * 0.002;
      +					var color = "rgba(255,255,255, " + f.toFixed(2) + ")";
      +					this.renderLink( ctx, start_node_slotpos, node.getConnectionPos(true,i), link, true, f, color );
      +				}
      +			}
      +	}
      +	ctx.globalAlpha = 1;
      +}
      +
      +/**
      +* draws a link between two points
      +* @method renderLink
      +**/
      +LGraphCanvas.prototype.renderLink = function( ctx, a, b, link, skip_border, flow, color )
      +{
      +	if(!this.highquality_render)
      +	{
      +		ctx.beginPath();
      +		ctx.moveTo(a[0],a[1]);
      +		ctx.lineTo(b[0],b[1]);
      +		ctx.stroke();
      +		return;
      +	}
      +
      +	var dist = distance(a,b);
      +
      +	if(this.render_connections_border && this.scale > 0.6)
      +		ctx.lineWidth = this.connections_width + 4;
      +
      +	//choose color
      +	if( !color && link )
      +		color = LGraphCanvas.link_type_colors[ link.type ];
      +	if(!color)
      +		color = this.default_link_color;
      +
      +	if( link != null && this.highlighted_links[ link.id ] )
      +		color = "#FFF";
      +
      +	//begin line shape
      +	ctx.beginPath();
      +
      +	if(this.render_curved_connections) //splines
      +	{
      +		ctx.moveTo(a[0],a[1]);
      +		ctx.bezierCurveTo(a[0] + dist*0.25, a[1],
      +							b[0] - dist*0.25 , b[1],
      +							b[0] ,b[1] );
      +	}
      +	else //lines
      +	{
      +		ctx.moveTo(a[0]+10,a[1]);
      +		ctx.lineTo(((a[0]+10) + (b[0]-10))*0.5,a[1]);
      +		ctx.lineTo(((a[0]+10) + (b[0]-10))*0.5,b[1]);
      +		ctx.lineTo(b[0]-10,b[1]);
      +	}
      +
      +	//rendering the outline of the connection can be a little bit slow
      +	if(this.render_connections_border && this.scale > 0.6 && !skip_border)
      +	{
      +		ctx.strokeStyle = "rgba(0,0,0,0.5)";
      +		ctx.stroke();
      +	}
      +
      +	ctx.lineWidth = this.connections_width;
      +	ctx.fillStyle = ctx.strokeStyle = color;
      +	ctx.stroke();
      +	//end line shape
      +
      +	//render arrow in the middle
      +	if( this.render_connection_arrows && this.scale >= 0.6 )
      +	{
      +		//render arrow
      +		if(this.render_connection_arrows && this.scale > 0.6)
      +		{
      +			//compute two points in the connection
      +			var pos = this.computeConnectionPoint(a,b,0.5);
      +			var pos2 = this.computeConnectionPoint(a,b,0.51);
      +
      +			//compute the angle between them so the arrow points in the right direction
      +			var angle = 0;
      +			if(this.render_curved_connections)
      +				angle = -Math.atan2( pos2[0] - pos[0], pos2[1] - pos[1]);
      +			else
      +				angle = b[1] > a[1] ? 0 : Math.PI;
      +
      +			//render arrow
      +			ctx.save();
      +			ctx.translate(pos[0],pos[1]);
      +			ctx.rotate(angle);
      +			ctx.beginPath();
      +			ctx.moveTo(-5,-5);
      +			ctx.lineTo(0,+5);
      +			ctx.lineTo(+5,-5);
      +			ctx.fill();
      +			ctx.restore();
      +		}
      +	}
      +
      +	//render flowing points
      +	if(flow)
      +	{
      +		for(var i = 0; i < 5; ++i)
      +		{
      +			var f = (LiteGraph.getTime() * 0.001 + (i * 0.2)) % 1;
      +			var pos = this.computeConnectionPoint(a,b,f);
      +			ctx.beginPath();
      +			ctx.arc(pos[0],pos[1],5,0,2*Math.PI);
      +			ctx.fill();
      +		}
      +	}
      +}
      +
      +LGraphCanvas.prototype.computeConnectionPoint = function(a,b,t)
      +{
      +	var dist = distance(a,b);
      +	var p0 = a;
      +	var p1 = [ a[0] + dist*0.25, a[1] ];
      +	var p2 = [ b[0] - dist*0.25, b[1] ];
      +	var p3 = b;
      +
      +	var c1 = (1-t)*(1-t)*(1-t);
      +	var c2 = 3*((1-t)*(1-t))*t;
      +	var c3 = 3*(1-t)*(t*t);
      +	var c4 = t*t*t;
      +
      +	var x = c1*p0[0] + c2*p1[0] + c3*p2[0] + c4*p3[0];
      +	var y = c1*p0[1] + c2*p1[1] + c3*p2[1] + c4*p3[1];
      +	return [x,y];
      +}
      +
      +/**
      +* draws the widgets stored inside a node
      +* @method drawNodeWidgets
      +**/
      +LGraphCanvas.prototype.drawNodeWidgets = function( node, posY, ctx, active_widget )
      +{
      +	if(!node.widgets || !node.widgets.length)
      +		return 0;
      +	var width = node.size[0];
      +	var widgets = node.widgets;
      +	posY += 2;
      +	var H = LiteGraph.NODE_WIDGET_HEIGHT;
      +	var show_text = this.scale > 0.5;
      +
      +	for(var i = 0; i < widgets.length; ++i)
      +	{
      +		var w = widgets[i];
      +		var y = posY;
      +		if(w.y)
      +			y = w.y;
      +		w.last_y = y;
      +		ctx.strokeStyle = "#AAA";
      +		ctx.fillStyle = "#222";
      +		ctx.textAlign = "left";
      +
      +		switch( w.type )
      +		{
      +			case "button": 
      +				if(w.clicked)
      +				{
      +					ctx.fillStyle = "#AAA";
      +					w.clicked = false;
      +					this.dirty_canvas = true;
      +				}
      +				ctx.fillRect(10,y,width-20,H);
      +				ctx.strokeRect(10,y,width-20,H);
      +				if(show_text)
      +				{
      +					ctx.textAlign = "center";
      +					ctx.fillStyle = "#AAA";
      +					ctx.fillText( w.name, width*0.5, y + H*0.7 );
      +				}
      +				break;
      +			case "slider": 
      +				ctx.fillStyle = "#111";
      +				ctx.fillRect(10,y,width-20,H);
      +				var range = w.options.max - w.options.min;
      +				var nvalue = (w.value - w.options.min) / range;
      +				ctx.fillStyle = active_widget == w ? "#89A" : "#678";
      +				ctx.fillRect(10,y,nvalue*(width-20),H);
      +				ctx.strokeRect(10,y,width-20,H);
      +				if(show_text)
      +				{
      +					ctx.textAlign = "center";
      +					ctx.fillStyle = "#DDD";
      +					ctx.fillText( w.name + "  " + Number(w.value).toFixed(3), width*0.5, y + H*0.7 );
      +				}
      +				break;
      +			case "number":
      +			case "combo":
      +				ctx.textAlign = "left";
      +				ctx.strokeStyle = "#AAA";
      +				ctx.fillStyle = "#111";
      +				ctx.beginPath();
      +				ctx.roundRect( 10, posY, width - 20, H,H*0.5 );
      +				ctx.fill();
      +				ctx.stroke();
      +				ctx.fillStyle = "#AAA";
      +				ctx.beginPath();
      +				ctx.moveTo( 26, posY + 5 );
      +				ctx.lineTo( 16, posY + H*0.5 );
      +				ctx.lineTo( 26, posY + H - 5 );
      +				ctx.moveTo( width - 26, posY + 5 );
      +				ctx.lineTo( width - 16, posY + H*0.5 );
      +				ctx.lineTo( width - 26, posY + H - 5 );
      +				ctx.fill();
      +				if(show_text)
      +				{
      +					ctx.fillStyle = "#999";
      +					ctx.fillText( w.name, 30, y + H*0.7 );
      +					ctx.fillStyle = "#DDD";
      +					ctx.textAlign = "right";
      +					if(w.type == "number")
      +						ctx.fillText( Number(w.value).toFixed( w.options.precision !== undefined ? w.options.precision : 3), width - 40, y + H*0.7 );
      +					else
      +						ctx.fillText( w.value, width - 40, y + H*0.7 );
      +				}
      +				break;
      +			default:
      +				break;
      +		}
      +		posY += H + 4;
      +	}
      +	ctx.textAlign = "left";
      +}
      +
      +/**
      +* process an event on widgets 
      +* @method processNodeWidgets
      +**/
      +LGraphCanvas.prototype.processNodeWidgets = function( node, pos, event, active_widget )
      +{
      +	if(!node.widgets || !node.widgets.length)
      +		return null;
      +
      +	var x = pos[0] - node.pos[0];
      +	var y = pos[1] - node.pos[1];
      +	var width = node.size[0];
      +	var that = this;
      +
      +	for(var i = 0; i < node.widgets.length; ++i)
      +	{
      +		var w = node.widgets[i];
      +		if( w == active_widget || (x > 6 && x < (width - 12) && y > w.last_y && y < (w.last_y + LiteGraph.NODE_WIDGET_HEIGHT)) )
      +		{
      +			//inside widget
      +			switch( w.type )
      +			{
      +				case "button": 
      +					if(w.callback)
      +						setTimeout( function(){	w.callback( w, that, node, pos ); }, 20 );
      +					w.clicked = true;
      +					this.dirty_canvas = true;
      +					break;
      +				case "slider": 
      +					var range = w.options.max - w.options.min;
      +					var nvalue = Math.clamp( (x - 10) / (width - 20), 0, 1);
      +					w.value = w.options.min + (w.options.max - w.options.min) * nvalue;
      +					if(w.callback)
      +						setTimeout( function(){	w.callback( w.value, that, node, pos ); }, 20 );
      +					this.dirty_canvas = true;
      +					break;
      +				case "number": 
      +				case "combo": 
      +					if(event.type == "mousemove" && w.type == "number")
      +					{
      +						w.value += (event.deltaX * 0.1) * (w.options.step || 1);
      +						if(w.options.min != null && w.value < w.options.min)
      +							w.value = w.options.min;
      +						if(w.options.max != null && w.value > w.options.max)
      +							w.value = w.options.max;
      +					}
      +					else if( event.type == "mousedown" )
      +					{
      +						var delta = ( x < 40 ? -1 : ( x > width - 40 ? 1 : 0) );
      +						if (w.type == "number")
      +						{
      +							w.value += delta * 0.1 * (w.options.step || 1);
      +							if(w.options.min != null && w.value < w.options.min)
      +								w.value = w.options.min;
      +							if(w.options.max != null && w.value > w.options.max)
      +								w.value = w.options.max;
      +						}
      +						else if(delta)
      +						{
      +							var index = w.options.values.indexOf( w.value ) + delta;
      +							if( index >= w.options.values.length )
      +								index = 0;
      +							if( index < 0 )
      +								index = w.options.values.length - 1;
      +							w.value = w.options.values[ index ];
      +						}
      +					}
      +					if(w.callback)
      +						setTimeout( function(){	w.callback( w.value, that, node, pos ); }, 20 );
      +					this.dirty_canvas = true;
      +					break;
      +			}
      +
      +			return w;
      +		}
      +	}
      +	return null;
      +}
      +
      +/**
      +* draws every group area in the background
      +* @method drawGroups
      +**/
      +LGraphCanvas.prototype.drawGroups = function(canvas, ctx)
      +{
      +	if(!this.graph)
      +		return;
      +
      +	var groups = this.graph._groups;
      +
      +	ctx.save();
      +	ctx.globalAlpha = 0.5;
      +	ctx.font = "24px Arial";
      +
      +	for(var i = 0; i < groups.length; ++i)
      +	{
      +		var group = groups[i];
      +
      +		if(!overlapBounding( this.visible_area, group._bounding ))
      +			continue; //out of the visible area
      +
      +		ctx.fillStyle = group.color || "#335";
      +		ctx.strokeStyle = group.color || "#335";
      +		var pos = group._pos;
      +		var size = group._size;
      +		ctx.globalAlpha = 0.25;
      +		ctx.beginPath();
      +		ctx.rect( pos[0] + 0.5, pos[1] + 0.5, size[0], size[1] );
      +		ctx.fill();
      +		ctx.globalAlpha = 1;
      +		ctx.stroke();
      +
      +		ctx.beginPath();
      +		ctx.moveTo( pos[0] + size[0], pos[1] + size[1] );
      +		ctx.lineTo( pos[0] + size[0] - 10, pos[1] + size[1] );
      +		ctx.lineTo( pos[0] + size[0], pos[1] + size[1] - 10 );
      +		ctx.fill();
      +
      +		ctx.fillText( group.title, pos[0] + 4, pos[1] + 24 );
      +	}
      +
      +	ctx.restore();
      +}
      +
      +/**
      +* resizes the canvas to a given size, if no size is passed, then it tries to fill the parentNode
      +* @method resize
      +**/
      +LGraphCanvas.prototype.resize = function(width, height)
      +{
      +	if(!width && !height)
      +	{
      +		var parent = this.canvas.parentNode;
      +		width = parent.offsetWidth;
      +		height = parent.offsetHeight;
      +	}
      +
      +	if(this.canvas.width == width && this.canvas.height == height)
      +		return;
      +
      +	this.canvas.width = width;
      +	this.canvas.height = height;
      +	this.bgcanvas.width = this.canvas.width;
      +	this.bgcanvas.height = this.canvas.height;
      +	this.setDirty(true,true);
      +}
      +
      +/**
      +* switches to live mode (node shapes are not rendered, only the content)
      +* this feature was designed when graphs where meant to create user interfaces
      +* @method switchLiveMode
      +**/
      +LGraphCanvas.prototype.switchLiveMode = function(transition)
      +{
      +	if(!transition)
      +	{
      +		this.live_mode = !this.live_mode;
      +		this.dirty_canvas = true;
      +		this.dirty_bgcanvas = true;
      +		return;
      +	}
      +
      +	var self = this;
      +	var delta = this.live_mode ? 1.1 : 0.9;
      +	if(this.live_mode)
      +	{
      +		this.live_mode = false;
      +		this.editor_alpha = 0.1;
      +	}
      +
      +	var t = setInterval(function() {
      +		self.editor_alpha *= delta;
      +		self.dirty_canvas = true;
      +		self.dirty_bgcanvas = true;
      +
      +		if(delta < 1  && self.editor_alpha < 0.01)
      +		{
      +			clearInterval(t);
      +			if(delta < 1)
      +				self.live_mode = true;
      +		}
      +		if(delta > 1 && self.editor_alpha > 0.99)
      +		{
      +			clearInterval(t);
      +			self.editor_alpha = 1;
      +		}
      +	},1);
      +}
      +
      +LGraphCanvas.prototype.onNodeSelectionChange = function(node)
      +{
      +	return; //disabled
      +}
      +
      +LGraphCanvas.prototype.touchHandler = function(event)
      +{
      +	//alert("foo");
      +    var touches = event.changedTouches,
      +        first = touches[0],
      +        type = "";
      +
      +         switch(event.type)
      +    {
      +        case "touchstart": type = "mousedown"; break;
      +        case "touchmove":  type = "mousemove"; break;
      +        case "touchend":   type = "mouseup"; break;
      +        default: return;
      +    }
      +
      +             //initMouseEvent(type, canBubble, cancelable, view, clickCount,
      +    //           screenX, screenY, clientX, clientY, ctrlKey,
      +    //           altKey, shiftKey, metaKey, button, relatedTarget);
      +
      +	var window = this.getCanvasWindow();
      +	var document = window.document;
      +
      +    var simulatedEvent = document.createEvent("MouseEvent");
      +    simulatedEvent.initMouseEvent(type, true, true, window, 1,
      +                              first.screenX, first.screenY,
      +                              first.clientX, first.clientY, false,
      +                              false, false, false, 0/*left*/, null);
      +	first.target.dispatchEvent(simulatedEvent);
      +    event.preventDefault();
      +}
      +
      +/* CONTEXT MENU ********************/
      +
      +LGraphCanvas.onGroupAdd = function(info,entry,mouse_event)
      +{
      +	var canvas = LGraphCanvas.active_canvas;
      +	var ref_window = canvas.getCanvasWindow();
      +		
      +	var group = new LiteGraph.LGraphGroup();
      +	group.pos = canvas.convertEventToCanvas( mouse_event );
      +	canvas.graph.add( group );
      +}
      +
      +LGraphCanvas.onMenuAdd = function( node, options, e, prev_menu )
      +{
      +	var canvas = LGraphCanvas.active_canvas;
      +	var ref_window = canvas.getCanvasWindow();
      +
      +	var values = LiteGraph.getNodeTypesCategories();
      +	var entries = [];
      +	for(var i in values)
      +		if(values[i])
      +			entries.push({ value: values[i], content: values[i], has_submenu: true });
      +
      +	var menu = new LiteGraph.ContextMenu( entries, { event: e, callback: inner_clicked, parentMenu: prev_menu }, ref_window);
      +
      +	function inner_clicked( v, option, e )
      +	{
      +		var category = v.value;
      +		var node_types = LiteGraph.getNodeTypesInCategory( category, canvas.filter );
      +		var values = [];
      +		for(var i in node_types)
      +			values.push( { content: node_types[i].title, value: node_types[i].type });
      +
      +		new LiteGraph.ContextMenu( values, {event: e, callback: inner_create, parentMenu: menu }, ref_window);
      +		return false;
      +	}
      +
      +	function inner_create( v, e )
      +	{
      +		var first_event = prev_menu.getFirstEvent();
      +		var node = LiteGraph.createNode( v.value );
      +		if(node)
      +		{
      +			node.pos = canvas.convertEventToCanvas( first_event );
      +			canvas.graph.add( node );
      +		}
      +	}
      +
      +	return false;
      +}
      +
      +LGraphCanvas.onMenuCollapseAll = function()
      +{
      +
      +}
      +
      +
      +LGraphCanvas.onMenuNodeEdit = function()
      +{
      +
      +}
      +
      +LGraphCanvas.showMenuNodeOptionalInputs = function( v, options, e, prev_menu, node )
      +{
      +	if(!node)
      +		return;
      +
      +	var that = this;
      +	var canvas = LGraphCanvas.active_canvas;
      +	var ref_window = canvas.getCanvasWindow();
      +
      +	var options = node.optional_inputs;
      +	if(node.onGetInputs)
      +		options = node.onGetInputs();
      +
      +	var entries = [];
      +	if(options)
      +		for (var i in options)
      +		{
      +			var entry = options[i];
      +			if(!entry)
      +			{
      +				entries.push(null);
      +				continue;
      +			}
      +			var label = entry[0];
      +			if(entry[2] && entry[2].label)
      +				label = entry[2].label;
      +			var data = {content: label, value: entry};
      +			if(entry[1] == LiteGraph.ACTION)
      +				data.className = "event";
      +			entries.push(data);
      +		}
      +
      +	if(this.onMenuNodeInputs)
      +		entries = this.onMenuNodeInputs( entries );
      +
      +	if(!entries.length)
      +		return;
      +
      +	var menu = new LiteGraph.ContextMenu(entries, { event: e, callback: inner_clicked, parentMenu: prev_menu, node: node }, ref_window);
      +
      +	function inner_clicked(v, e, prev)
      +	{
      +		if(!node)
      +			return;
      +
      +		if(v.callback)
      +			v.callback.call( that, node, v, e, prev );
      +
      +		if(v.value)
      +		{
      +			node.addInput(v.value[0],v.value[1], v.value[2]);
      +			node.setDirtyCanvas(true,true);
      +		}
      +	}
      +
      +	return false;
      +}
      +
      +LGraphCanvas.showMenuNodeOptionalOutputs = function( v, options, e, prev_menu, node )
      +{
      +	if(!node)
      +		return;
      +
      +	var that = this;
      +	var canvas = LGraphCanvas.active_canvas;
      +	var ref_window = canvas.getCanvasWindow();
      +
      +	var options = node.optional_outputs;
      +	if(node.onGetOutputs)
      +		options = node.onGetOutputs();
      +
      +	var entries = [];
      +	if(options)
      +		for (var i in options)
      +		{
      +			var entry = options[i];
      +			if(!entry) //separator?
      +			{
      +				entries.push(null);
      +				continue;
      +			}
      +
      +			if(node.flags && node.flags.skip_repeated_outputs && node.findOutputSlot(entry[0]) != -1)
      +				continue; //skip the ones already on
      +			var label = entry[0];
      +			if(entry[2] && entry[2].label)
      +				label = entry[2].label;
      +			var data = {content: label, value: entry};
      +			if(entry[1] == LiteGraph.EVENT)
      +				data.className = "event";
      +			entries.push(data);
      +		}
      +
      +	if(this.onMenuNodeOutputs)
      +		entries = this.onMenuNodeOutputs( entries );
      +
      +	if(!entries.length)
      +		return;
      +
      +	var menu = new LiteGraph.ContextMenu(entries, {event: e, callback: inner_clicked, parentMenu: prev_menu, node: node }, ref_window);
      +
      +	function inner_clicked( v, e, prev )
      +	{
      +		if(!node)
      +			return;
      +
      +		if(v.callback)
      +			v.callback.call( that, node, v, e, prev );
      +
      +		if(!v.value)
      +			return;
      +
      +		var value = v.value[1];
      +
      +		if(value && (value.constructor === Object || value.constructor === Array)) //submenu why?
      +		{
      +			var entries = [];
      +			for(var i in value)
      +				entries.push({ content: i, value: value[i]});
      +			new LiteGraph.ContextMenu( entries, { event: e, callback: inner_clicked, parentMenu: prev_menu, node: node });
      +			return false;
      +		}
      +		else
      +		{
      +			node.addOutput( v.value[0], v.value[1], v.value[2]);
      +			node.setDirtyCanvas(true,true);
      +		}
      +
      +	}
      +
      +	return false;
      +}
      +
      +LGraphCanvas.onShowMenuNodeProperties = function( value, options, e, prev_menu, node )
      +{
      +	if(!node || !node.properties)
      +		return;
      +
      +	var that = this;
      +	var canvas = LGraphCanvas.active_canvas;
      +	var ref_window = canvas.getCanvasWindow();
      +
      +	var entries = [];
      +		for (var i in node.properties)
      +		{
      +			var value = node.properties[i] !== undefined ? node.properties[i] : " ";
      +			//value could contain invalid html characters, clean that
      +			value = LGraphCanvas.decodeHTML(value);
      +			entries.push({content: "<span class='property_name'>" + i + "</span>" + "<span class='property_value'>" + value + "</span>", value: i});
      +		}
      +	if(!entries.length)
      +		return;
      +
      +	var menu = new LiteGraph.ContextMenu(entries, {event: e, callback: inner_clicked, parentMenu: prev_menu, allow_html: true, node: node },ref_window);
      +
      +	function inner_clicked( v, options, e, prev )
      +	{
      +		if(!node)
      +			return;
      +		var rect = this.getBoundingClientRect();
      +		canvas.showEditPropertyValue( node, v.value, { position: [rect.left, rect.top] });
      +	}
      +
      +	return false;
      +}
      +
      +LGraphCanvas.decodeHTML = function( str )
      +{
      +	var e = document.createElement("div");
      +	e.innerText = str;
      +	return e.innerHTML;
      +}
      +
      +LGraphCanvas.onResizeNode = function( value, options, e, menu, node )
      +{
      +	if(!node)
      +		return;
      +	node.size = node.computeSize();
      +	node.setDirtyCanvas(true,true);
      +}
      +
      +
      +LGraphCanvas.onShowTitleEditor = function( value, options, e, menu, node )
      +{
      +	var input_html = "";
      +
      +	var dialog = document.createElement("div");
      +	dialog.className = "graphdialog";
      +	dialog.innerHTML = "<span class='name'>Title</span><input autofocus type='text' class='value'/><button>OK</button>";
      +	var input = dialog.querySelector("input");
      +	if(input)
      +	{
      +		input.value = node.title;
      +		input.addEventListener("keydown", function(e){
      +			if(e.keyCode != 13)
      +				return;
      +			inner();
      +			e.preventDefault();
      +			e.stopPropagation();
      +		});
      +	}
      +
      +	var graphcanvas = LGraphCanvas.active_canvas;
      +	var canvas = graphcanvas.canvas;
      +
      +	var rect = canvas.getBoundingClientRect();
      +	var offsetx = -20;
      +	var offsety = -20;
      +	if(rect)
      +	{
      +		offsetx -= rect.left;
      +		offsety -= rect.top;
      +	}
      +
      +	if( event )
      +	{
      +		dialog.style.left = (event.pageX + offsetx) + "px";
      +		dialog.style.top = (event.pageY + offsety)+ "px";
      +	}
      +	else
      +	{
      +		dialog.style.left = (canvas.width * 0.5 + offsetx) + "px";
      +		dialog.style.top = (canvas.height * 0.5 + offsety) + "px";
      +	}
      +
      +	var button = dialog.querySelector("button");
      +	button.addEventListener("click", inner );
      +	canvas.parentNode.appendChild( dialog );
      +
      +	function inner()
      +	{
      +		setValue( input.value );
      +	}
      +
      +	function setValue(value)
      +	{
      +		node.title = value;
      +		dialog.parentNode.removeChild( dialog );
      +		node.setDirtyCanvas(true,true);
      +	}
      +}
      +
      +LGraphCanvas.prototype.showSearchBox = function(event)
      +{
      +	var that = this;
      +	var input_html = "";
      +
      +	var dialog = document.createElement("div");
      +	dialog.className = "graphdialog";
      +	dialog.innerHTML = "<span class='name'>Search</span> <input autofocus type='text' class='value'/><div class='helper'></div>";
      +	dialog.close = function()
      +	{
      +		that.search_box = null;
      +		dialog.parentNode.removeChild( dialog );
      +	}
      +
      +	dialog.addEventListener("mouseleave",function(e){
      +		 dialog.close();
      +	});
      +
      +	if(that.search_box)
      +		that.search_box.close();
      +	that.search_box = dialog;
      +
      +	var helper = dialog.querySelector(".helper");
      +
      +	var first = null;
      +	var timeout = null;
      +	var selected = null;
      +
      +	var input = dialog.querySelector("input");
      +	if(input)
      +	{
      +		input.addEventListener("keydown", function(e){
      +
      +			if(e.keyCode == 38) //UP
      +				changeSelection(false);
      +			else if(e.keyCode == 40) //DOWN
      +				changeSelection(true);
      +			else if(e.keyCode == 27) //ESC
      +				dialog.close();
      +			else if(e.keyCode == 13)
      +			{
      +				if(selected)
      +					select( selected.innerHTML )
      +				else if(first)
      +					select(first);
      +				else
      +					dialog.close();
      +			}
      +			else
      +			{
      +				if(timeout)
      +					clearInterval(timeout);
      +				timeout = setTimeout( refreshHelper, 10 );
      +				return;
      +			}
      +			e.preventDefault();
      +			e.stopPropagation();
      +		});
      +	}
      +
      +	var graphcanvas = LGraphCanvas.active_canvas;
      +	var canvas = graphcanvas.canvas;
      +
      +	var rect = canvas.getBoundingClientRect();
      +	var offsetx = -20;
      +	var offsety = -20;
      +	if(rect)
      +	{
      +		offsetx -= rect.left;
      +		offsety -= rect.top;
      +	}
      +
      +	if( event )
      +	{
      +		dialog.style.left = (event.pageX + offsetx) + "px";
      +		dialog.style.top = (event.pageY + offsety)+ "px";
      +	}
      +	else
      +	{
      +		dialog.style.left = (canvas.width * 0.5 + offsetx) + "px";
      +		dialog.style.top = (canvas.height * 0.5 + offsety) + "px";
      +	}
      +
      +	canvas.parentNode.appendChild( dialog );
      +	input.focus();
      +
      +	function select( name )
      +	{
      +		if(name)
      +		{
      +			if( that.onSearchBoxSelection )
      +				that.onSearchBoxSelection( name, event, graphcanvas );
      +			else
      +			{
      +				var node = LiteGraph.createNode( name );
      +				if(node)
      +				{
      +					node.pos = graphcanvas.convertEventToCanvas( event );
      +					graphcanvas.graph.add( node );
      +				}
      +			}
      +		}
      +
      +		dialog.close();
      +	}
      +
      +	function changeSelection( forward )
      +	{
      +		var prev = selected;
      +		if(selected)
      +			selected.classList.remove("selected");
      +		if(!selected)
      +			selected = forward ? helper.childNodes[0] : helper.childNodes[ helper.childNodes.length ];
      +		else
      +		{
      +			selected = forward ? selected.nextSibling : selected.previousSibling;
      +			if(!selected)
      +				selected = prev;
      +		}
      +		if(!selected)
      +			return;
      +		selected.classList.add("selected");
      +		selected.scrollIntoView();
      +	}
      +
      +	function refreshHelper()
      +	{
      +		timeout = null;
      +		var str = input.value;
      +		first = null;
      +		helper.innerHTML = "";
      +		if(!str)
      +			return;
      +
      +		if( that.onSearchBox )
      +			that.onSearchBox( help, str, graphcanvas );
      +		else
      +			for( var i in LiteGraph.registered_node_types )
      +				if(i.indexOf(str) != -1)
      +				{
      +					var help = document.createElement("div");
      +					if(!first) first = i;
      +					help.innerText = i;
      +					help.className = "help-item";
      +					help.addEventListener("click", function(e){
      +						select( this.innerText );
      +					});
      +					helper.appendChild(help);
      +				}
      +	}
      +
      +	return dialog;
      +}
      +
      +LGraphCanvas.prototype.showEditPropertyValue = function( node, property, options )
      +{
      +	if(!node || node.properties[ property ] === undefined )
      +		return;
      +
      +	options = options || {};
      +	var that = this;
      +
      +	var type = "string";
      +
      +	if(node.properties[ property ] !== null)
      +		type = typeof(node.properties[ property ]);
      +
      +	//for arrays
      +	if(type == "object")
      +	{
      +		if( node.properties[ property ].length )
      +			type = "array";
      +	}
      +
      +	var info = null;
      +	if(node.getPropertyInfo)
      +		info = node.getPropertyInfo(property);
      +	if(node.properties_info)
      +	{
      +		for(var i = 0; i < node.properties_info.length; ++i)
      +		{
      +			if( node.properties_info[i].name == property )
      +			{
      +				info = node.properties_info[i];
      +				break;
      +			}
      +		}
      +	}
      +
      +	if(info !== undefined && info !== null && info.type )
      +		type = info.type;
      +
      +	var input_html = "";
      +
      +	if(type == "string" || type == "number" || type == "array")
      +		input_html = "<input autofocus type='text' class='value'/>";
      +	else if(type == "enum" && info.values)
      +	{
      +		input_html = "<select autofocus type='text' class='value'>";
      +		for(var i in info.values)
      +		{
      +			var v = info.values.constructor === Array ? info.values[i] : i;
      +			input_html += "<option value='"+v+"' "+(v == node.properties[property] ? "selected" : "")+">"+info.values[i]+"</option>";
      +		}
      +		input_html += "</select>";
      +	}
      +	else if(type == "boolean")
      +	{
      +		input_html = "<input autofocus type='checkbox' class='value' "+(node.properties[property] ? "checked" : "")+"/>";
      +	}
      +	else
      +	{
      +		console.warn("unknown type: " + type );
      +		return;
      +	}
      +
      +	var dialog = this.createDialog( "<span class='name'>" + property + "</span>"+input_html+"<button>OK</button>" , options );
      +
      +	if(type == "enum" && info.values)
      +	{
      +		var input = dialog.querySelector("select");
      +		input.addEventListener("change", function(e){
      +			setValue( e.target.value );
      +			//var index = e.target.value;
      +			//setValue( e.options[e.selectedIndex].value );
      +		});
      +	}
      +	else if(type == "boolean")
      +	{
      +		var input = dialog.querySelector("input");
      +		if(input)
      +		{
      +			input.addEventListener("click", function(e){
      +				setValue( !!input.checked );
      +			});
      +		}
      +	}
      +	else
      +	{
      +		var input = dialog.querySelector("input");
      +		if(input)
      +		{
      +			input.value = node.properties[ property ] !== undefined ? node.properties[ property ] : "";
      +			input.addEventListener("keydown", function(e){
      +				if(e.keyCode != 13)
      +					return;
      +				inner();
      +				e.preventDefault();
      +				e.stopPropagation();
      +			});
      +		}
      +	}
      +
      +	var button = dialog.querySelector("button");
      +	button.addEventListener("click", inner );
      +
      +	function inner()
      +	{
      +		setValue( input.value );
      +	}
      +
      +	function setValue(value)
      +	{
      +		if(typeof( node.properties[ property ] ) == "number")
      +			value = Number(value);
      +		if(type == "array")
      +			value = value.split(",").map(Number);
      +		node.properties[ property ] = value;
      +		if(node._graph)
      +			node._graph._version++;
      +		if(node.onPropertyChanged)
      +			node.onPropertyChanged( property, value );
      +		dialog.close();
      +		node.setDirtyCanvas(true,true);
      +	}
      +}
      +
      +LGraphCanvas.prototype.createDialog = function( html, options )
      +{
      +	options = options || {};
      +
      +	var dialog = document.createElement("div");
      +	dialog.className = "graphdialog";
      +	dialog.innerHTML = html;
      +
      +	var rect = this.canvas.getBoundingClientRect();
      +	var offsetx = -20;
      +	var offsety = -20;
      +	if(rect)
      +	{
      +		offsetx -= rect.left;
      +		offsety -= rect.top;
      +	}
      +
      +	if( options.position )
      +	{
      +		offsetx += options.position[0];
      +		offsety += options.position[1];
      +	}
      +	else if( options.event )
      +	{
      +		offsetx += options.event.pageX;
      +		offsety += options.event.pageY;
      +	}
      +	else //centered
      +	{
      +		offsetx += this.canvas.width * 0.5;
      +		offsety += this.canvas.height * 0.5;
      +	}
      +
      +	dialog.style.left = offsetx + "px";
      +	dialog.style.top = offsety + "px";
      +
      +	this.canvas.parentNode.appendChild( dialog );
      +
      +	dialog.close = function()
      +	{
      +		if(this.parentNode)
      +			this.parentNode.removeChild( this );
      +	}
      +
      +	return dialog;
      +}
      +
      +LGraphCanvas.onMenuNodeCollapse = function( value, options, e, menu, node )
      +{
      +	node.collapse();
      +}
      +
      +LGraphCanvas.onMenuNodePin = function( value, options, e, menu, node )
      +{
      +	node.pin();
      +}
      +
      +LGraphCanvas.onMenuNodeMode = function( value, options, e, menu, node )
      +{
      +	new LiteGraph.ContextMenu(["Always","On Event","On Trigger","Never"], {event: e, callback: inner_clicked, parentMenu: menu, node: node });
      +
      +	function inner_clicked(v)
      +	{
      +		if(!node)
      +			return;
      +		switch(v)
      +		{
      +			case "On Event": node.mode = LiteGraph.ON_EVENT; break;
      +			case "On Trigger": node.mode = LiteGraph.ON_TRIGGER; break;
      +			case "Never": node.mode = LiteGraph.NEVER; break;
      +			case "Always":
      +			default:
      +				node.mode = LiteGraph.ALWAYS; break;
      +		}
      +	}
      +
      +	return false;
      +}
      +
      +LGraphCanvas.onMenuNodeColors = function( value, options, e, menu, node )
      +{
      +	if(!node)
      +		throw("no node for color");
      +
      +	var values = [];
      +	values.push({ value:null, content:"<span style='display: block; padding-left: 4px;'>No color</span>" });
      +
      +	for(var i in LGraphCanvas.node_colors)
      +	{
      +		var color = LGraphCanvas.node_colors[i];
      +		var value = { value:i, content:"<span style='display: block; color: #999; padding-left: 4px; border-left: 8px solid "+color.color+"; background-color:"+color.bgcolor+"'>"+i+"</span>" };
      +		values.push(value);
      +	}
      +	new LiteGraph.ContextMenu( values, { event: e, callback: inner_clicked, parentMenu: menu, node: node });
      +
      +	function inner_clicked(v)
      +	{
      +		if(!node)
      +			return;
      +
      +		var color = v.value ? LGraphCanvas.node_colors[ v.value ] : null;
      +		if(color)
      +		{
      +			if(node.constructor === LiteGraph.LGraphGroup)
      +				node.color = color.groupcolor;
      +			else
      +			{
      +				node.color = color.color;
      +				node.bgcolor = color.bgcolor;
      +			}
      +		}
      +		else
      +		{
      +			delete node.color;
      +			delete node.bgcolor;
      +		}
      +		node.setDirtyCanvas(true,true);
      +	}
      +
      +	return false;
      +}
      +
      +LGraphCanvas.onMenuNodeShapes = function( value, options, e, menu, node )
      +{
      +	if(!node)
      +		throw("no node passed");
      +
      +	new LiteGraph.ContextMenu( LiteGraph.VALID_SHAPES, { event: e, callback: inner_clicked, parentMenu: menu, node: node });
      +
      +	function inner_clicked(v)
      +	{
      +		if(!node)
      +			return;
      +		node.shape = v;
      +		node.setDirtyCanvas(true);
      +	}
      +
      +	return false;
      +}
      +
      +LGraphCanvas.onMenuNodeRemove = function( value, options, e, menu, node )
      +{
      +	if(!node)
      +		throw("no node passed");
      +
      +	if(node.removable === false)
      +		return;
      +
      +	node.graph.remove(node);
      +	node.setDirtyCanvas(true,true);
      +}
      +
      +LGraphCanvas.onMenuNodeClone = function( value, options, e, menu, node )
      +{
      +	if(node.clonable == false) return;
      +	var newnode = node.clone();
      +	if(!newnode)
      +		return;
      +	newnode.pos = [node.pos[0]+5,node.pos[1]+5];
      +	node.graph.add(newnode);
      +	node.setDirtyCanvas(true,true);
      +}
      +
      +LGraphCanvas.node_colors = {
      +	"red": { color:"#322", bgcolor:"#533", groupcolor: "#A88" },
      +	"brown": { color:"#332922", bgcolor:"#593930", groupcolor: "#b06634" },
      +	"green": { color:"#232", bgcolor:"#353", groupcolor: "#8A8" },
      +	"blue": { color:"#223", bgcolor:"#335", groupcolor: "#88A" },
      +	"pale_blue": { color:"#2a363b", bgcolor:"#3f5159", groupcolor: "#3f789e" },
      +	"cyan": { color:"#233", bgcolor:"#355", groupcolor: "#8AA" },
      +	"purple": { color:"#323", bgcolor:"#535", groupcolor: "#a1309b" },
      +	"yellow": { color:"#432", bgcolor:"#653", groupcolor: "#b58b2a" },
      +	"black": { color:"#222", bgcolor:"#000", groupcolor: "#444" }
      +};
      +
      +LGraphCanvas.prototype.getCanvasMenuOptions = function()
      +{
      +	var options = null;
      +	if(this.getMenuOptions)
      +		options = this.getMenuOptions();
      +	else
      +	{
      +		options = [
      +			{ content:"Add Node", has_submenu: true, callback: LGraphCanvas.onMenuAdd },
      +			{ content:"Add Group", callback: LGraphCanvas.onGroupAdd }
      +			//{content:"Collapse All", callback: LGraphCanvas.onMenuCollapseAll }
      +		];
      +
      +		if(this._graph_stack && this._graph_stack.length > 0)
      +			options = [{content:"Close subgraph", callback: this.closeSubgraph.bind(this) },null].concat(options);
      +	}
      +
      +	if(this.getExtraMenuOptions)
      +	{
      +		var extra = this.getExtraMenuOptions(this,options);
      +		if(extra)
      +			options = options.concat( extra );
      +	}
      +
      +	return options;
      +}
      +
      +//called by processContextMenu to extract the menu list
      +LGraphCanvas.prototype.getNodeMenuOptions = function( node )
      +{
      +	var options = null;
      +
      +	if(node.getMenuOptions)
      +		options = node.getMenuOptions(this);
      +	else
      +		options = [
      +			{content:"Inputs", has_submenu: true, disabled:true, callback: LGraphCanvas.showMenuNodeOptionalInputs },
      +			{content:"Outputs", has_submenu: true, disabled:true, callback: LGraphCanvas.showMenuNodeOptionalOutputs },
      +			null,
      +			{content:"Properties", has_submenu: true, callback: LGraphCanvas.onShowMenuNodeProperties },
      +			null,
      +			{content:"Title", callback: LGraphCanvas.onShowTitleEditor },
      +			{content:"Mode", has_submenu: true, callback: LGraphCanvas.onMenuNodeMode },
      +			{content:"Resize", callback: LGraphCanvas.onResizeNode },
      +			{content:"Collapse", callback: LGraphCanvas.onMenuNodeCollapse },
      +			{content:"Pin", callback: LGraphCanvas.onMenuNodePin },
      +			{content:"Colors", has_submenu: true, callback: LGraphCanvas.onMenuNodeColors },
      +			{content:"Shapes", has_submenu: true, callback: LGraphCanvas.onMenuNodeShapes },
      +			null
      +		];
      +
      +	if(node.getExtraMenuOptions)
      +	{
      +		var extra = node.getExtraMenuOptions(this);
      +		if(extra)
      +		{
      +			extra.push(null);
      +			options = extra.concat( options );
      +		}
      +	}
      +
      +	if( node.clonable !== false )
      +			options.push({content:"Clone", callback: LGraphCanvas.onMenuNodeClone });
      +	if( node.removable !== false )
      +			options.push(null,{content:"Remove", callback: LGraphCanvas.onMenuNodeRemove });
      +
      +	if(node.onGetInputs)
      +	{
      +		var inputs = node.onGetInputs();
      +		if(inputs && inputs.length)
      +			options[0].disabled = false;
      +	}
      +
      +	if(node.onGetOutputs)
      +	{
      +		var outputs = node.onGetOutputs();
      +		if(outputs && outputs.length )
      +			options[1].disabled = false;
      +	}
      +
      +	if(node.graph && node.graph.onGetNodeMenuOptions )
      +		node.graph.onGetNodeMenuOptions( options, node );
      +
      +	return options;
      +}
      +
      +LGraphCanvas.prototype.getGroupMenuOptions = function( node )
      +{
      +	var o = [
      +		{content:"Title", callback: LGraphCanvas.onShowTitleEditor },
      +		{content:"Color", has_submenu: true, callback: LGraphCanvas.onMenuNodeColors },
      +		null,
      +		{content:"Remove", callback: LGraphCanvas.onMenuNodeRemove }
      +	];
      +
      +	return o;
      +}
      +
      +LGraphCanvas.prototype.processContextMenu = function( node, event )
      +{
      +	var that = this;
      +	var canvas = LGraphCanvas.active_canvas;
      +	var ref_window = canvas.getCanvasWindow();
      +
      +	var menu_info = null;
      +	var options = { event: event, callback: inner_option_clicked, node: node };
      +
      +	//check if mouse is in input
      +	var slot = null;
      +	if(node)
      +	{
      +		slot = node.getSlotInPosition( event.canvasX, event.canvasY );
      +		LGraphCanvas.active_node = node;
      +	}
      +
      +	if(slot) //on slot
      +	{
      +		menu_info = [];
      +		menu_info.push( slot.locked ? "Cannot remove"  : { content: "Remove Slot", slot: slot } );
      +		menu_info.push( { content: "Rename Slot", slot: slot } );
      +		options.title = (slot.input ? slot.input.type : slot.output.type) || "*";
      +		if(slot.input && slot.input.type == LiteGraph.ACTION)
      +			options.title = "Action";
      +		if(slot.output && slot.output.type == LiteGraph.EVENT)
      +			options.title = "Event";
      +	}
      +	else
      +	{
      +		if( node ) //on node
      +			menu_info = this.getNodeMenuOptions(node);
      +		else 
      +		{
      +			var group = this.graph.getGroupOnPos( event.canvasX, event.canvasY );
      +			if( group ) //on group
      +			{
      +				options.node = group;
      +				menu_info = this.getGroupMenuOptions( group );
      +			}
      +			else
      +				menu_info = this.getCanvasMenuOptions();
      +		}
      +	}
      +
      +	//show menu
      +	if(!menu_info)
      +		return;
      +
      +	var menu = new LiteGraph.ContextMenu( menu_info, options, ref_window );
      +
      +	function inner_option_clicked( v, options, e )
      +	{
      +		if(!v)
      +			return;
      +
      +		if(v.content == "Remove Slot")
      +		{
      +			var info = v.slot;
      +			if(info.input)
      +				node.removeInput( info.slot );
      +			else if(info.output)
      +				node.removeOutput( info.slot );
      +			return;
      +		}
      +		else if( v.content == "Rename Slot")
      +		{
      +			var info = v.slot;
      +			var dialog = that.createDialog( "<span class='name'>Name</span><input type='text'/><button>OK</button>" , options );
      +			var input = dialog.querySelector("input");
      +			dialog.querySelector("button").addEventListener("click",function(e){
      +				if(input.value)
      +				{
      +					var slot_info = info.input ? node.getInputInfo( info.slot ) : node.getOutputInfo( info.slot );
      +					if( slot_info )
      +						slot_info.label = input.value;
      +					that.setDirty(true);
      +				}
      +				dialog.close();
      +			});
      +		}
      +
      +		//if(v.callback)
      +		//	return v.callback.call(that, node, options, e, menu, that, event );
      +	}
      +}
      +
      +
      +
      +
      +
      +
      +//API *************************************************
      +//like rect but rounded corners
      +if(this.CanvasRenderingContext2D)
      +CanvasRenderingContext2D.prototype.roundRect = function (x, y, width, height, radius, radius_low) {
      +  if ( radius === undefined ) {
      +    radius = 5;
      +  }
      +
      +  if(radius_low === undefined)
      +	 radius_low  = radius;
      +
      +  this.moveTo(x + radius, y);
      +  this.lineTo(x + width - radius, y);
      +  this.quadraticCurveTo(x + width, y, x + width, y + radius);
      +
      +  this.lineTo(x + width, y + height - radius_low);
      +  this.quadraticCurveTo(x + width, y + height, x + width - radius_low, y + height);
      +  this.lineTo(x + radius_low, y + height);
      +  this.quadraticCurveTo(x, y + height, x, y + height - radius_low);
      +  this.lineTo(x, y + radius);
      +  this.quadraticCurveTo(x, y, x + radius, y);
      +}
      +
      +function compareObjects(a,b)
      +{
      +	for(var i in a)
      +		if(a[i] != b[i])
      +			return false;
      +	return true;
      +}
      +LiteGraph.compareObjects = compareObjects;
      +
      +function distance(a,b)
      +{
      +	return Math.sqrt( (b[0] - a[0]) * (b[0] - a[0]) + (b[1] - a[1]) * (b[1] - a[1]) );
      +}
      +LiteGraph.distance = distance;
      +
      +function colorToString(c)
      +{
      +	return "rgba(" + Math.round(c[0] * 255).toFixed() + "," + Math.round(c[1] * 255).toFixed() + "," + Math.round(c[2] * 255).toFixed() + "," + (c.length == 4 ? c[3].toFixed(2) : "1.0") + ")";
      +}
      +LiteGraph.colorToString = colorToString;
      +
      +function isInsideRectangle( x,y, left, top, width, height)
      +{
      +	if (left < x && (left + width) > x &&
      +		top < y && (top + height) > y)
      +		return true;
      +	return false;
      +}
      +LiteGraph.isInsideRectangle = isInsideRectangle;
      +
      +//[minx,miny,maxx,maxy]
      +function growBounding( bounding, x,y)
      +{
      +	if(x < bounding[0])
      +		bounding[0] = x;
      +	else if(x > bounding[2])
      +		bounding[2] = x;
      +
      +	if(y < bounding[1])
      +		bounding[1] = y;
      +	else if(y > bounding[3])
      +		bounding[3] = y;
      +}
      +LiteGraph.growBounding = growBounding;
      +
      +//point inside boundin box
      +function isInsideBounding(p,bb)
      +{
      +	if (p[0] < bb[0][0] ||
      +		p[1] < bb[0][1] ||
      +		p[0] > bb[1][0] ||
      +		p[1] > bb[1][1])
      +		return false;
      +	return true;
      +}
      +LiteGraph.isInsideBounding = isInsideBounding;
      +
      +//boundings overlap, format: [ startx, starty, width, height ]
      +function overlapBounding(a,b)
      +{
      +	var A_end_x = a[0] + a[2];
      +	var A_end_y = a[1] + a[3];
      +	var B_end_x = b[0] + b[2];
      +	var B_end_y = b[1] + b[3];
      +
      +	if ( a[0] > B_end_x ||
      +		a[1] > B_end_y ||
      +		A_end_x < b[0] ||
      +		A_end_y < b[1])
      +		return false;
      +	return true;
      +}
      +LiteGraph.overlapBounding = overlapBounding;
      +
      +//Convert a hex value to its decimal value - the inputted hex must be in the
      +//	format of a hex triplet - the kind we use for HTML colours. The function
      +//	will return an array with three values.
      +function hex2num(hex) {
      +	if(hex.charAt(0) == "#") hex = hex.slice(1); //Remove the '#' char - if there is one.
      +	hex = hex.toUpperCase();
      +	var hex_alphabets = "0123456789ABCDEF";
      +	var value = new Array(3);
      +	var k = 0;
      +	var int1,int2;
      +	for(var i=0;i<6;i+=2) {
      +		int1 = hex_alphabets.indexOf(hex.charAt(i));
      +		int2 = hex_alphabets.indexOf(hex.charAt(i+1));
      +		value[k] = (int1 * 16) + int2;
      +		k++;
      +	}
      +	return(value);
      +}
      +
      +LiteGraph.hex2num = hex2num;
      +
      +//Give a array with three values as the argument and the function will return
      +//	the corresponding hex triplet.
      +function num2hex(triplet) {
      +	var hex_alphabets = "0123456789ABCDEF";
      +	var hex = "#";
      +	var int1,int2;
      +	for(var i=0;i<3;i++) {
      +		int1 = triplet[i] / 16;
      +		int2 = triplet[i] % 16;
      +
      +		hex += hex_alphabets.charAt(int1) + hex_alphabets.charAt(int2);
      +	}
      +	return(hex);
      +}
      +
      +LiteGraph.num2hex = num2hex;
      +
      +/* LiteGraph GUI elements used for canvas editing *************************************/
      +
      +/**
      +* ContextMenu from LiteGUI
      +*
      +* @class ContextMenu
      +* @constructor
      +* @param {Array} values (allows object { title: "Nice text", callback: function ... })
      +* @param {Object} options [optional] Some options:\
      +* - title: title to show on top of the menu
      +* - callback: function to call when an option is clicked, it receives the item information
      +* - ignore_item_callbacks: ignores the callback inside the item, it just calls the options.callback
      +* - event: you can pass a MouseEvent, this way the ContextMenu appears in that position
      +*/
      +function ContextMenu( values, options )
      +{
      +	options = options || {};
      +	this.options = options;
      +	var that = this;
      +
      +	//to link a menu with its parent
      +	if(options.parentMenu)
      +	{
      +		if( options.parentMenu.constructor !== this.constructor )
      +		{
      +			console.error("parentMenu must be of class ContextMenu, ignoring it");
      +			options.parentMenu = null;
      +		}
      +		else
      +		{
      +			this.parentMenu = options.parentMenu;
      +			this.parentMenu.lock = true;
      +			this.parentMenu.current_submenu = this;
      +		}
      +	}
      +
      +	if(options.event && options.event.constructor !== MouseEvent && options.event.constructor !== CustomEvent)
      +	{
      +		console.error("Event passed to ContextMenu is not of type MouseEvent or CustomEvent. Ignoring it.");
      +		options.event = null;
      +	}
      +
      +	var root = document.createElement("div");
      +	root.className = "litegraph litecontextmenu litemenubar-panel";
      +	root.style.minWidth = 100;
      +	root.style.minHeight = 100;
      +	root.style.pointerEvents = "none";
      +	setTimeout( function() { root.style.pointerEvents = "auto"; },100); //delay so the mouse up event is not caugh by this element
      +
      +	//this prevents the default context browser menu to open in case this menu was created when pressing right button
      +	root.addEventListener("mouseup", function(e){
      +		e.preventDefault(); return true;
      +	}, true);
      +	root.addEventListener("contextmenu", function(e) {
      +		if(e.button != 2) //right button
      +			return false;
      +		e.preventDefault();
      +		return false;
      +	},true);
      +
      +	root.addEventListener("mousedown", function(e){
      +		if(e.button == 2)
      +		{
      +			that.close();
      +			e.preventDefault(); return true;
      +		}
      +	}, 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;
      +
      +	//title
      +	if(options.title)
      +	{
      +		var element = document.createElement("div");
      +		element.className = "litemenu-title";
      +		element.innerHTML = options.title;
      +		root.appendChild(element);
      +	}
      +
      +	//entries
      +	var num = 0;
      +	for(var i in values)
      +	{
      +		var name = values.constructor == Array ? values[i] : i;
      +		if( name != null && name.constructor !== String )
      +			name = name.content === undefined ? String(name) : name.content;
      +		var value = values[i];
      +		this.addItem( name, value, options );
      +		num++;
      +	}
      +
      +	//close on leave
      +	root.addEventListener("mouseleave", function(e) {
      +		if(that.lock)
      +			return;
      +		that.close(e);
      +	});
      +
      +	//insert before checking position
      +	var root_document = document;
      +	if(options.event)
      +		root_document = options.event.target.ownerDocument;
      +
      +	if(!root_document)
      +		root_document = document;
      +	root_document.body.appendChild(root);
      +
      +	//compute best position
      +	var left = options.left || 0;
      +	var top = options.top || 0;
      +	if(options.event)
      +	{
      +		left = (options.event.pageX - 10);
      +		top = (options.event.pageY - 10);
      +		if(options.title)
      +			top -= 20;
      +
      +		if(options.parentMenu)
      +		{
      +			var rect = options.parentMenu.root.getBoundingClientRect();
      +			left = rect.left + rect.width;
      +		}
      +
      +		var body_rect = document.body.getBoundingClientRect();
      +		var root_rect = root.getBoundingClientRect();
      +
      +		if(left > (body_rect.width - root_rect.width - 10))
      +			left = (body_rect.width - root_rect.width - 10);
      +		if(top > (body_rect.height - root_rect.height - 10))
      +			top = (body_rect.height - root_rect.height - 10);
      +	}
      +
      +	root.style.left = left + "px";
      +	root.style.top = top  + "px";
      +}
      +
      +ContextMenu.prototype.addItem = function( name, value, options )
      +{
      +	var that = this;
      +	options = options || {};
      +
      +	var element = document.createElement("div");
      +	element.className = "litemenu-entry submenu";
      +
      +	var disabled = false;
      +
      +	if(value === null)
      +	{
      +		element.classList.add("separator");
      +		//element.innerHTML = "<hr/>"
      +		//continue;
      +	}
      +	else
      +	{
      +		element.innerHTML = value && value.title ? value.title : name;
      +		element.value = value;
      +
      +		if(value)
      +		{
      +			if(value.disabled)
      +			{
      +				disabled = true;
      +				element.classList.add("disabled");
      +			}
      +			if(value.submenu || value.has_submenu)
      +				element.classList.add("has_submenu");
      +		}
      +
      +		if(typeof(value) == "function")
      +		{
      +			element.dataset["value"] = name;
      +			element.onclick_callback = value;
      +		}
      +		else
      +			element.dataset["value"] = value;
      +
      +		if(value.className)
      +			element.className += " " + value.className;
      +	}
      +
      +	this.root.appendChild(element);
      +	if(!disabled)
      +		element.addEventListener("click", inner_onclick);
      +	if(options.autoopen)
      +		element.addEventListener("mouseenter", inner_over);
      +
      +	function inner_over(e)
      +	{
      +		var value = this.value;
      +		if(!value || !value.has_submenu)
      +			return;
      +		inner_onclick.call(this,e);
      +	}
      +
      +	//menu option clicked
      +	function inner_onclick(e) {
      +		var value = this.value;
      +		var close_parent = true;
      +
      +		if(that.current_submenu)
      +			that.current_submenu.close(e);
      +
      +		//global callback
      +		if(options.callback)
      +		{
      +			var r = options.callback.call( this, value, options, e, that, options.node );
      +			if(r === true)
      +				close_parent = false;
      +		}
      +
      +		//special cases
      +		if(value)
      +		{
      +			if (value.callback && !options.ignore_item_callbacks && value.disabled !== true )  //item callback
      +			{
      +				var r = value.callback.call( this, value, options, e, that, options.node );
      +				if(r === true)
      +					close_parent = false;
      +			}
      +			if(value.submenu)
      +			{
      +				if(!value.submenu.options)
      +					throw("ContextMenu submenu needs options");
      +				var submenu = new that.constructor( value.submenu.options, {
      +					callback: value.submenu.callback,
      +					event: e,
      +					parentMenu: that,
      +					ignore_item_callbacks: value.submenu.ignore_item_callbacks,
      +					title: value.submenu.title,
      +					autoopen: options.autoopen
      +				});
      +				close_parent = false;
      +			}
      +		}
      +
      +		if(close_parent && !that.lock)
      +			that.close();
      +	}
      +
      +	return element;
      +}
      +
      +ContextMenu.prototype.close = function(e, ignore_parent_menu)
      +{
      +	if(this.root.parentNode)
      +		this.root.parentNode.removeChild( this.root );
      +	if(this.parentMenu && !ignore_parent_menu)
      +	{
      +		this.parentMenu.lock = false;
      +		this.parentMenu.current_submenu = null;
      +		if( e === undefined )
      +			this.parentMenu.close();
      +		else if( e && !ContextMenu.isCursorOverElement( e, this.parentMenu.root) )
      +		{
      +			ContextMenu.trigger( this.parentMenu.root, "mouseleave", e );
      +		}
      +	}
      +	if(this.current_submenu)
      +		this.current_submenu.close(e, true);
      +}
      +
      +//this code is used to trigger events easily (used in the context menu mouseleave
      +ContextMenu.trigger = function(element, event_name, params, origin)
      +{
      +	var evt = document.createEvent( 'CustomEvent' );
      +	evt.initCustomEvent( event_name, true,true, params ); //canBubble, cancelable, detail
      +	evt.srcElement = origin;
      +	if( element.dispatchEvent )
      +		element.dispatchEvent( evt );
      +	else if( element.__events )
      +		element.__events.dispatchEvent( evt );
      +	//else nothing seems binded here so nothing to do
      +	return evt;
      +}
      +
      +//returns the top most menu
      +ContextMenu.prototype.getTopMenu = function()
      +{
      +	if( this.options.parentMenu )
      +		return this.options.parentMenu.getTopMenu();
      +	return this;
      +}
      +
      +ContextMenu.prototype.getFirstEvent = function()
      +{
      +	if( this.options.parentMenu )
      +		return this.options.parentMenu.getFirstEvent();
      +	return this.options.event;
      +}
      +
      +
      +
      +ContextMenu.isCursorOverElement = function( event, element )
      +{
      +	var left = event.pageX;
      +	var top = event.pageY;
      +	var rect = element.getBoundingClientRect();
      +	if(!rect)
      +		return false;
      +	if(top > rect.top && top < (rect.top + rect.height) &&
      +		left > rect.left && left < (rect.left + rect.width) )
      +		return true;
      +	return false;
      +}
      +
      +
      +
      +LiteGraph.ContextMenu = ContextMenu;
      +
      +LiteGraph.closeAllContextMenus = function( ref_window )
      +{
      +	ref_window = ref_window || window;
      +
      +	var elements = ref_window.document.querySelectorAll(".litecontextmenu");
      +	if(!elements.length)
      +		return;
      +
      +	var result = [];
      +	for(var i = 0; i < elements.length; i++)
      +		result.push(elements[i]);
      +
      +	for(var i in result)
      +	{
      +		if(result[i].close)
      +			result[i].close();
      +		else if(result[i].parentNode)
      +			result[i].parentNode.removeChild( result[i] );
      +	}
      +}
      +
      +LiteGraph.extendClass = function ( target, origin )
      +{
      +	for(var i in origin) //copy class properties
      +	{
      +		if(target.hasOwnProperty(i))
      +			continue;
      +		target[i] = origin[i];
      +	}
      +
      +	if(origin.prototype) //copy prototype properties
      +		for(var i in origin.prototype) //only enumerables
      +		{
      +			if(!origin.prototype.hasOwnProperty(i))
      +				continue;
      +
      +			if(target.prototype.hasOwnProperty(i)) //avoid overwritting existing ones
      +				continue;
      +
      +			//copy getters
      +			if(origin.prototype.__lookupGetter__(i))
      +				target.prototype.__defineGetter__(i, origin.prototype.__lookupGetter__(i));
      +			else
      +				target.prototype[i] = origin.prototype[i];
      +
      +			//and setters
      +			if(origin.prototype.__lookupSetter__(i))
      +				target.prototype.__defineSetter__(i, origin.prototype.__lookupSetter__(i));
      +		}
      +}
      +
      +//used to create nodes from wrapping functions
      +LiteGraph.getParameterNames = function(func) {
      +    return (func + '')
      +      .replace(/[/][/].*$/mg,'') // strip single-line comments
      +      .replace(/\s+/g, '') // strip white space
      +      .replace(/[/][*][^/*]*[*][/]/g, '') // strip multi-line comments  /**/
      +      .split('){', 1)[0].replace(/^[^(]*[(]/, '') // extract the parameters
      +      .replace(/=[^,]+/g, '') // strip any ES6 defaults
      +      .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 ||
      +		  window.mozRequestAnimationFrame    ||
      +		  (function( callback ){
      +			window.setTimeout(callback, 1000 / 60);
      +		  });
      +}
      +
      +})(this);
      +
      +if(typeof(exports) != "undefined")
      +	exports.LiteGraph = this.LiteGraph;
       
           
      diff --git a/guides/README.md b/guides/README.md index aa92945fe..a4fa4ba1d 100644 --- a/guides/README.md +++ b/guides/README.md @@ -7,6 +7,41 @@ Here is a list of useful info when working with LiteGraph LGraphNode is the base class used for all the nodes classes. To extend the other classes all the methods contained in LGraphNode.prototype are copyed to the classes when registered. +When you create a new node type you do not have to inherit from that class, when the node is registered all the methods are copied to your node prototype. + +## Node settings + +There are several settings that could be defined per node: +* **size**: ```[width,height]``` +* **properties**: object containing the properties that could be configured by the user +* **shape**: the shape of the object (could be LiteGraph.BOX,LiteGraph.ROUND,LiteGraph.CARD) +* **flags**: several flags +⋅⋅* **resizable**: if it can be resized dragging the corner +⋅⋅* **horizontal**: if the slots should be placed horizontally on the top and bottom of the node +⋅⋅* **clip_area**: clips the content when rendering the node + +There are several callbacks that could be defined: +* **onAdded**: when added to graph +* **onRemoved**: when removed from graph +* **onStart**: when the graph starts playing +* **onStop**: when the graph stops playing +* **onDrawForeground**: render the inside widgets inside the node +* **onDrawBackground**: render the background area inside the node (only in edit mode) +* **onMouseDown,onMouseMove,onMouseUp,onMouseEnter,onMouseLeave** +* **onDblClick**: double clicked in the editor +* **onExecute**: execute the node +* **onPropertyChanged**: when a property is changed in the panel (return true to skip default behaviour) +* **onGetInputs**: returns an array of possible inputs +* **onGetOutputs**: returns an array of possible outputs +* **onSerialize**: before serializing +* **onSelected**: selected in the editor +* **onDeselected**: deselected from the editor +* **onDropItem**: DOM item dropped over the node +* **onDropFile**: file dropped over the node +* **onConnectInput**: if returns false the incoming connection will be canceled +* **onConnectionsChange**: a connection changed (new one or removed) (LiteGraph.INPUT or LiteGraph.OUTPUT, slot, true if connected, link_info, input_info ) + + ### Node slots Every node could have several slots, stored in node.inputs and node.outputs. @@ -23,8 +58,11 @@ Slots have the next information: * **type**: string specifying the data type traveling through this link * **link or links**: depending if the slot is input or ouput contains the id of the link or an array of ids * **label**: optional, string used to rename the name as shown in the canvas. - - To retrieve the data traveling through a link you can call node.getInputData or node.getOutputData + * **dir**: optional, could be LiteGraph.UP, LiteGraph.RIGHT, LiteGraph.DOWN, LiteGraph.LEFT + * **color_on**: color to render when it is connected + * **color_off**: color to render when it is not connected + + To retrieve the data traveling through a link you can call ```node.getInputData``` or ```node.getOutputData``` ### Define your Graph Node diff --git a/imgs/node_graph_example.PNG b/imgs/node_graph_example.PNG deleted file mode 100644 index 37ef90b2c..000000000 Binary files a/imgs/node_graph_example.PNG and /dev/null differ diff --git a/imgs/node_graph_example.png b/imgs/node_graph_example.png new file mode 100644 index 000000000..313af60c2 Binary files /dev/null and b/imgs/node_graph_example.png differ diff --git a/src/litegraph-editor.js b/src/litegraph-editor.js index b7c638524..5219d29b0 100755 --- a/src/litegraph-editor.js +++ b/src/litegraph-editor.js @@ -182,6 +182,7 @@ Editor.prototype.addMiniWindow = function(w,h) miniwindow.className = "litegraph miniwindow"; miniwindow.innerHTML = ""; var canvas = miniwindow.querySelector("canvas"); + var that = this; var graphcanvas = new LGraphCanvas(canvas, this.graph); graphcanvas.show_info = false; @@ -189,12 +190,24 @@ Editor.prototype.addMiniWindow = function(w,h) graphcanvas.scale = 0.25; graphcanvas.allow_dragnodes = false; graphcanvas.allow_interaction = false; + graphcanvas.render_shadows = false; + graphcanvas.max_zoom = 0.25; this.miniwindow_graphcanvas = graphcanvas; graphcanvas.onClear = function() { graphcanvas.scale = 0.25; graphcanvas.allow_dragnodes = false; graphcanvas.allow_interaction = false; }; + graphcanvas.onRenderBackground = function(canvas, ctx) + { + ctx.strokeStyle = "#567"; + var tl = that.graphcanvas.convertOffsetToCanvas([0,0]); + var br = that.graphcanvas.convertOffsetToCanvas([that.graphcanvas.canvas.width,that.graphcanvas.canvas.height]); + tl = this.convertCanvasToOffset( tl ); + br = this.convertCanvasToOffset( br ); + ctx.lineWidth = 1; + ctx.strokeRect( Math.floor(tl[0]) + 0.5, Math.floor(tl[1]) + 0.5, Math.floor(br[0] - tl[0]), Math.floor(br[1] - tl[1]) ); + } miniwindow.style.position = "absolute"; miniwindow.style.top = "4px"; diff --git a/src/litegraph.js b/src/litegraph.js index 275adec12..6c0b2b44c 100755 --- a/src/litegraph.js +++ b/src/litegraph.js @@ -24,6 +24,8 @@ var LiteGraph = global.LiteGraph = { + CANVAS_GRID_SIZE: 10, + NODE_TITLE_HEIGHT: 20, NODE_SLOT_HEIGHT: 15, NODE_WIDGET_HEIGHT: 20, @@ -31,7 +33,6 @@ var LiteGraph = global.LiteGraph = { NODE_MIN_WIDTH: 50, NODE_COLLAPSED_RADIUS: 10, NODE_COLLAPSED_WIDTH: 80, - CANVAS_GRID_SIZE: 10, NODE_TITLE_COLOR: "#999", NODE_TEXT_SIZE: 14, NODE_TEXT_COLOR: "#AAA", @@ -40,12 +41,16 @@ var LiteGraph = global.LiteGraph = { NODE_DEFAULT_BGCOLOR: "#444", NODE_DEFAULT_BOXCOLOR: "#888", NODE_DEFAULT_SHAPE: "box", + + LINK_COLOR: "#AAD", + EVENT_LINK_COLOR: "#F85", + CONNECTING_LINK_COLOR: "#AFA", + MAX_NUMBER_OF_NODES: 1000, //avoid infinite loops DEFAULT_POSITION: [100,100],//default node position - node_images_path: "", - VALID_SHAPES: ["default","box","round","card"], //,"circle" + //shapes are used for nodes but also for slots BOX_SHAPE: 1, ROUND_SHAPE: 2, CIRCLE_SHAPE: 3, @@ -64,12 +69,19 @@ var LiteGraph = global.LiteGraph = { NEVER: 2, ON_TRIGGER: 3, + UP: 1, + DOWN:2, + LEFT:3, + RIGHT:4, + CENTER:5, + NORMAL_TITLE: 0, NO_TITLE: 1, TRANSPARENT_TITLE: 2, AUTOHIDE_TITLE: 3, proxy: null, //used to redirect calls + node_images_path: "", debug: false, throw_errors: true, @@ -2716,8 +2728,8 @@ LGraphNode.prototype.connect = function( slot, target_node, target_slot ) target_node.disconnectInput( target_slot ); //why here?? - this.setDirtyCanvas(false,true); - this.graph.connectionChange( this ); + //this.setDirtyCanvas(false,true); + //this.graph.connectionChange( this ); var output = this.outputs[slot]; @@ -2754,6 +2766,8 @@ LGraphNode.prototype.connect = function( slot, target_node, target_slot ) this.onConnectionsChange( LiteGraph.OUTPUT, slot, true, link_info, output ); //link_info has been created now, so its updated if(target_node.onConnectionsChange) target_node.onConnectionsChange( LiteGraph.INPUT, target_slot, true, link_info, input ); + if( this.graph && this.graph.onNodeConnectionChange ) + this.graph.onNodeConnectionChange( LiteGraph.OUTPUT, this, slot, target_node, target_slot ); } this.setDirtyCanvas(false,true); @@ -2793,7 +2807,7 @@ LGraphNode.prototype.disconnectOutput = function( slot, target_node ) if(!output.links || output.links.length == 0) return false; - //one of the links + //one of the output links in this slot if(target_node) { if(target_node.constructor === Number) @@ -2819,11 +2833,15 @@ LGraphNode.prototype.disconnectOutput = function( slot, target_node ) target_node.onConnectionsChange( LiteGraph.INPUT, link_info.target_slot, false, link_info, input ); //link_info hasnt been modified so its ok if(this.onConnectionsChange) this.onConnectionsChange( LiteGraph.OUTPUT, slot, false, link_info, output ); + if( this.graph && this.graph.onNodeConnectionChange ) + this.graph.onNodeConnectionChange( LiteGraph.OUTPUT, this, slot ); + if( this.graph && this.graph.onNodeConnectionChange ) + this.graph.onNodeConnectionChange( LiteGraph.INPUT, target_node, link_info.target_slot ); break; } } } - else //all the links + else //all the links in this output slot { for(var i = 0, l = output.links.length; i < l; i++) { @@ -2842,10 +2860,14 @@ LGraphNode.prototype.disconnectOutput = function( slot, target_node ) input.link = null; //remove other side link if(target_node.onConnectionsChange) target_node.onConnectionsChange( LiteGraph.INPUT, link_info.target_slot, false, link_info, input ); //link_info hasnt been modified so its ok + if( this.graph && this.graph.onNodeConnectionChange ) + this.graph.onNodeConnectionChange( LiteGraph.INPUT, target_node, link_info.target_slot ); } delete this.graph.links[ link_id ]; //remove the link from the links pool if(this.onConnectionsChange) this.onConnectionsChange( LiteGraph.OUTPUT, slot, false, link_info, output ); + if( this.graph && this.graph.onNodeConnectionChange ) + this.graph.onNodeConnectionChange( LiteGraph.OUTPUT, this, slot ); } output.links = null; } @@ -2952,9 +2974,18 @@ LGraphNode.prototype.getConnectionPos = function( is_input, slot_number ) else if(!is_input && this.outputs.length > slot_number && this.outputs[slot_number].pos) return [this.pos[0] + this.outputs[slot_number].pos[0],this.pos[1] + this.outputs[slot_number].pos[1]]; - if(!is_input) //output - return [this.pos[0] + this.size[0] + 1, this.pos[1] + 10 + slot_number * LiteGraph.NODE_SLOT_HEIGHT + (this.constructor.slot_start_y || 0)]; - return [this.pos[0] , this.pos[1] + 10 + slot_number * LiteGraph.NODE_SLOT_HEIGHT + (this.constructor.slot_start_y || 0) ]; + //horizontal distributed slots + if(this.flags.horizontal) + { + if(is_input) + return [this.pos[0] + (slot_number + 0.5) * (this.size[0] / (this.inputs.length)), this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT ]; + return [this.pos[0] + (slot_number + 0.5) * (this.size[0] / (this.outputs.length)), this.pos[1] + this.size[1] ]; + } + + //default + if(is_input) + return [this.pos[0] , this.pos[1] + 10 + slot_number * LiteGraph.NODE_SLOT_HEIGHT + (this.constructor.slot_start_y || 0) ]; + return [this.pos[0] + this.size[0] + 1, this.pos[1] + 10 + slot_number * LiteGraph.NODE_SLOT_HEIGHT + (this.constructor.slot_start_y || 0)]; } /* Force align to grid */ @@ -3200,7 +3231,7 @@ LGraphGroup.prototype.setDirtyCanvas = LGraphNode.prototype.setDirtyCanvas; //********************************************************************************* /** -* The Global Scope. It contains all the registered node classes. +* This class is in charge of rendering one graph inside a canvas. And provides all the interaction required. * Valid callbacks are: onNodeSelected, onNodeDeselected, onShowNodePanel, onNodeDblClicked * * @class LGraphCanvas @@ -3227,7 +3258,7 @@ function LGraphCanvas( canvas, graph, options ) this.title_text_font = "bold "+LiteGraph.NODE_TEXT_SIZE+"px Arial"; this.inner_text_font = "normal "+LiteGraph.NODE_SUBTEXT_SIZE+"px Arial"; this.node_title_color = LiteGraph.NODE_TITLE_COLOR; - this.default_link_color = "#AAC"; + this.default_link_color = LiteGraph.LINK_COLOR; this.default_connection_color = { input_off: "#AAB", input_on: "#7F7", @@ -3484,6 +3515,10 @@ LGraphCanvas.prototype.setCanvas = function( canvas, skip_events ) LGraphCanvas.prototype._doNothing = function doNothing(e) { e.preventDefault(); return false; }; LGraphCanvas.prototype._doReturnTrue = function doNothing(e) { e.preventDefault(); return true; }; +/** +* binds mouse, keyboard, touch and drag events to the canvas +* @method bindEvents +**/ LGraphCanvas.prototype.bindEvents = function() { if( this._events_binded ) @@ -3532,6 +3567,10 @@ LGraphCanvas.prototype.bindEvents = function() this._events_binded = true; } +/** +* unbinds mouse events from the canvas +* @method unbindEvents +**/ LGraphCanvas.prototype.unbindEvents = function() { if( !this._events_binded ) @@ -3576,8 +3615,11 @@ LGraphCanvas.getFileExtension = function (url) return url.substr(point+1).toLowerCase(); } -//this file allows to render the canvas using WebGL instead of Canvas2D -//this is useful if you plant to render 3D objects inside your nodes +/** +* this function allows to render the canvas using WebGL instead of Canvas2D +* this is useful if you plant to render 3D objects inside your nodes, it uses litegl.js for webgl and canvas2DtoWebGL to emulate the Canvas2D calls in webGL +* @method enableWebGL +**/ LGraphCanvas.prototype.enableWebGL = function() { if(typeof(GL) === undefined) @@ -3599,55 +3641,6 @@ LGraphCanvas.prototype.enableWebGL = function() } -/* -LGraphCanvas.prototype.UIinit = function() -{ - var that = this; - $("#node-console input").change(function(e) - { - if(e.target.value == "") - return; - - var node = that.node_in_panel; - if(!node) - return; - - node.trace("] " + e.target.value, "#333"); - if(node.onConsoleCommand) - { - if(!node.onConsoleCommand(e.target.value)) - node.trace("command not found", "#A33"); - } - else if (e.target.value == "info") - { - node.trace("Special methods:"); - for(var i in node) - { - if(typeof(node[i]) == "function" && LGraphNode.prototype[i] == null && i.substr(0,2) != "on" && i[0] != "_") - node.trace(" + " + i); - } - } - else - { - try - { - eval("var _foo = function() { return ("+e.target.value+"); }"); - var result = _foo.call(node); - if(result) - node.trace(result.toString()); - delete window._foo; - } - catch(err) - { - node.trace("error: " + err, "#A33"); - } - } - - this.value = ""; - }); -} -*/ - /** * marks as dirty the canvas, this way it will be rendered again * @@ -3945,6 +3938,10 @@ LGraphCanvas.prototype.processMouseDown = function(e) return false; } +/** +* Called when a mouse move event has to be processed +* @method processMouseMove +**/ LGraphCanvas.prototype.processMouseMove = function(e) { if(this.autoresize) @@ -4132,6 +4129,10 @@ LGraphCanvas.prototype.processMouseMove = function(e) //this.graph.change(); } +/** +* Called when a mouse up event has to be processed +* @method processMouseUp +**/ LGraphCanvas.prototype.processMouseUp = function(e) { if(!this.graph) @@ -4279,7 +4280,10 @@ LGraphCanvas.prototype.processMouseUp = function(e) return false; } - +/** +* Called when a mouse wheel event has to be processed +* @method processMouseWheel +**/ LGraphCanvas.prototype.processMouseWheel = function(e) { if(!this.graph || !this.allow_dragcanvas) @@ -4309,6 +4313,10 @@ LGraphCanvas.prototype.processMouseWheel = function(e) return false; // prevent default } +/** +* retuns true if a position (in graph space) is on top of a node little corner box +* @method isOverNodeBox +**/ LGraphCanvas.prototype.isOverNodeBox = function( node, canvasx, canvasy ) { var title_height = LiteGraph.NODE_TITLE_HEIGHT; @@ -4317,6 +4325,10 @@ LGraphCanvas.prototype.isOverNodeBox = function( node, canvasx, canvasy ) return false; } +/** +* retuns true if a position (in graph space) is on top of a node input slot +* @method isOverNodeInput +**/ LGraphCanvas.prototype.isOverNodeInput = function(node, canvasx, canvasy, slot_pos ) { if(node.inputs) @@ -4337,6 +4349,10 @@ LGraphCanvas.prototype.isOverNodeInput = function(node, canvasx, canvasy, slot_p return -1; } +/** +* process a key event +* @method processKey +**/ LGraphCanvas.prototype.processKey = function(e) { if(!this.graph) @@ -4486,6 +4502,10 @@ LGraphCanvas.prototype.pasteFromClipboard = function() this.selectNodes( nodes ); } +/** +* process a item drop event on top the canvas +* @method processDrop +**/ LGraphCanvas.prototype.processDrop = function(e) { e.preventDefault(); @@ -4600,6 +4620,10 @@ LGraphCanvas.prototype.processNodeDeselected = function(node) this.onNodeDeselected(node); } +/** +* selects a given node (or adds it to the current selection) +* @method selectNode +**/ LGraphCanvas.prototype.selectNode = function( node, add_to_current_selection ) { if(node == null) @@ -4608,6 +4632,10 @@ LGraphCanvas.prototype.selectNode = function( node, add_to_current_selection ) this.selectNodes([node], add_to_current_selection ); } +/** +* selects several nodes (or adds them to the current selection) +* @method selectNodes +**/ LGraphCanvas.prototype.selectNodes = function( nodes, add_to_current_selection ) { if(!add_to_current_selection) @@ -4642,6 +4670,10 @@ LGraphCanvas.prototype.selectNodes = function( nodes, add_to_current_selection ) this.setDirty(true); } +/** +* removes a node from the current selection +* @method deselectNode +**/ LGraphCanvas.prototype.deselectNode = function( node ) { if(!node.selected) @@ -4664,6 +4696,10 @@ LGraphCanvas.prototype.deselectNode = function( node ) } } +/** +* removes all nodes from the current selection +* @method deselectAllNodes +**/ LGraphCanvas.prototype.deselectAllNodes = function() { if(!this.graph) @@ -4683,6 +4719,10 @@ LGraphCanvas.prototype.deselectAllNodes = function() this.setDirty(true); } +/** +* deletes all nodes in the current selection from the graph +* @method deleteSelectedNodes +**/ LGraphCanvas.prototype.deleteSelectedNodes = function() { for(var i in this.selected_nodes) @@ -4696,6 +4736,10 @@ LGraphCanvas.prototype.deleteSelectedNodes = function() this.setDirty(true); } +/** +* centers the camera on a given node +* @method centerOnNode +**/ LGraphCanvas.prototype.centerOnNode = function(node) { this.offset[0] = -node.pos[0] - node.size[0] * 0.5 + (this.canvas.width * 0.5 / this.scale); @@ -4703,6 +4747,10 @@ LGraphCanvas.prototype.centerOnNode = function(node) this.setDirty(true,true); } +/** +* adds some useful properties to a mouse event, like the position in graph coordinates +* @method adjustMouseEvent +**/ LGraphCanvas.prototype.adjustMouseEvent = function(e) { if(this.canvas) @@ -4727,6 +4775,10 @@ LGraphCanvas.prototype.adjustMouseEvent = function(e) e.canvasY = e.localY / this.scale - this.offset[1]; } +/** +* changes the zoom level of the graph (default is 1), you can pass also a place used to pivot the zoom +* @method setZoom +**/ LGraphCanvas.prototype.setZoom = function(value, zooming_center) { if(!zooming_center && this.canvas) @@ -4751,6 +4803,10 @@ LGraphCanvas.prototype.setZoom = function(value, zooming_center) this.dirty_bgcanvas = true; } +/** +* converts a coordinate in canvas2D space to graphcanvas space (NAME IS CONFUSION, SHOULD BE THE OTHER WAY AROUND) +* @method convertOffsetToCanvas +**/ LGraphCanvas.prototype.convertOffsetToCanvas = function( pos, out ) { out = out || []; @@ -4759,6 +4815,10 @@ LGraphCanvas.prototype.convertOffsetToCanvas = function( pos, out ) return out; } +/** +* converts a coordinate in graphcanvas space to canvas2D space (NAME IS CONFUSION, SHOULD BE THE OTHER WAY AROUND) +* @method convertCanvasToOffset +**/ LGraphCanvas.prototype.convertCanvasToOffset = function( pos, out ) { out = out || []; @@ -4773,22 +4833,30 @@ LGraphCanvas.prototype.convertEventToCanvas = function(e) return this.convertOffsetToCanvas([e.pageX - rect.left,e.pageY - rect.top]); } -LGraphCanvas.prototype.bringToFront = function(n) +/** +* brings a node to front (above all other nodes) +* @method bringToFront +**/ +LGraphCanvas.prototype.bringToFront = function(node) { - var i = this.graph._nodes.indexOf(n); + var i = this.graph._nodes.indexOf(node); if(i == -1) return; this.graph._nodes.splice(i,1); - this.graph._nodes.push(n); + this.graph._nodes.push(node); } -LGraphCanvas.prototype.sendToBack = function(n) +/** +* sends a node to the back (below all other nodes) +* @method sendToBack +**/ +LGraphCanvas.prototype.sendToBack = function(node) { - var i = this.graph._nodes.indexOf(n); + var i = this.graph._nodes.indexOf(node); if(i == -1) return; this.graph._nodes.splice(i,1); - this.graph._nodes.unshift(n); + this.graph._nodes.unshift(node); } /* Interaction */ @@ -4798,6 +4866,10 @@ LGraphCanvas.prototype.sendToBack = function(n) /* LGraphCanvas render */ var temp = new Float32Array(4); +/** +* checks which nodes are visible (inside the camera area) +* @method computeVisibleNodes +**/ LGraphCanvas.prototype.computeVisibleNodes = function( nodes, out ) { var visible_nodes = out || []; @@ -4819,6 +4891,10 @@ LGraphCanvas.prototype.computeVisibleNodes = function( nodes, out ) return visible_nodes; } +/** +* renders the whole canvas content, by rendering in two separated canvas, one containing the background grid and the connections, and one containing the nodes) +* @method draw +**/ LGraphCanvas.prototype.draw = function(force_canvas, force_bgcanvas) { if(!this.canvas) @@ -4846,6 +4922,10 @@ LGraphCanvas.prototype.draw = function(force_canvas, force_bgcanvas) this.frame += 1; } +/** +* draws the front canvas (the one containing all the nodes) +* @method drawFrontCanvas +**/ LGraphCanvas.prototype.drawFrontCanvas = function() { this.dirty_canvas = false; @@ -4932,12 +5012,12 @@ LGraphCanvas.prototype.drawFrontCanvas = function() var link_color = null; switch( this.connecting_output.type ) { - case LiteGraph.EVENT: link_color = "#F85"; break; + case LiteGraph.EVENT: link_color = LiteGraph.EVENT_LINK_COLOR; break; default: - link_color = "#AFA"; + link_color = LiteGraph.CONNECTING_LINK_COLOR; } //the connection being dragged by the mouse - this.renderLink( ctx, this.connecting_pos, [this.canvas_mouse[0],this.canvas_mouse[1]], null, false, null, link_color ); + this.renderLink( ctx, this.connecting_pos, [this.canvas_mouse[0],this.canvas_mouse[1]], null, false, null, link_color, this.connecting_output.dir || (this.connecting_node.flags.horizontal ? LiteGraph.DOWN : LiteGraph.RIGHT), LiteGraph.CENTER ); ctx.beginPath(); if( this.connecting_output.type === LiteGraph.EVENT || this.connecting_output.shape === LiteGraph.BOX_SHAPE ) @@ -4975,6 +5055,10 @@ LGraphCanvas.prototype.drawFrontCanvas = function() ctx.finish2D(); } +/** +* draws some useful stats in the corner of the canvas +* @method renderInfo +**/ LGraphCanvas.prototype.renderInfo = function( ctx, x, y ) { x = x || 0; @@ -4997,6 +5081,10 @@ LGraphCanvas.prototype.renderInfo = function( ctx, x, y ) ctx.restore(); } +/** +* draws the back canvas (the one containing the background and the connections) +* @method drawBackCanvas +**/ LGraphCanvas.prototype.drawBackCanvas = function() { var canvas = this.bgcanvas; @@ -5027,7 +5115,7 @@ LGraphCanvas.prototype.drawBackCanvas = function() var bg_already_painted = false; if(this.onRenderBackground) - bg_already_painted = this.onRenderBackground(); + bg_already_painted = this.onRenderBackground( canvas, ctx ); //reset in case of error ctx.restore(); @@ -5125,7 +5213,10 @@ LGraphCanvas.prototype.drawBackCanvas = function() var temp_vec2 = new Float32Array(2); -/* Renders the LGraphNode on the canvas */ +/** +* draws the given node inside the canvas +* @method drawNode +**/ LGraphCanvas.prototype.drawNode = function(node, ctx ) { var glow = false; @@ -5208,7 +5299,7 @@ LGraphCanvas.prototype.drawNode = function(node, ctx ) ctx.shadowColor = "transparent"; //connection slots - ctx.textAlign = "left"; + ctx.textAlign = node.flags.horizontal ? "center" : "left"; ctx.font = this.inner_text_font; var render_text = this.scale > 0.6; @@ -5232,7 +5323,7 @@ LGraphCanvas.prototype.drawNode = function(node, ctx ) if ( this.connecting_node && LiteGraph.isValidConnection( slot.type && out_slot.type ) ) ctx.globalAlpha = 0.4 * editor_alpha; - ctx.fillStyle = slot.link != null ? (slot.colorOn || this.default_connection_color.input_on) : (slot.colorOff || this.default_connection_color.input_off); + ctx.fillStyle = slot.link != null ? (slot.color_on || this.default_connection_color.input_on) : (slot.color_off || this.default_connection_color.input_off); var pos = node.getConnectionPos( true, i ); pos[0] -= node.pos[0]; @@ -5262,7 +5353,10 @@ LGraphCanvas.prototype.drawNode = function(node, ctx ) if(text) { ctx.fillStyle = LiteGraph.NODE_TEXT_COLOR; - ctx.fillText(text,pos[0] + 10,pos[1] + 5); + if( node.flags.horizontal || slot.dir == LiteGraph.UP ) + ctx.fillText(text,pos[0],pos[1] - 10); + else + ctx.fillText(text,pos[0] + 10,pos[1] + 5); } } } @@ -5271,7 +5365,7 @@ LGraphCanvas.prototype.drawNode = function(node, ctx ) if(this.connecting_node) ctx.globalAlpha = 0.4 * editor_alpha; - ctx.textAlign = "right"; + ctx.textAlign = node.flags.horizontal ? "center" : "right"; ctx.strokeStyle = "black"; if(node.outputs) for(var i = 0; i < node.outputs.length; i++) @@ -5284,7 +5378,7 @@ LGraphCanvas.prototype.drawNode = function(node, ctx ) if( max_y < pos[1] + LiteGraph.NODE_SLOT_HEIGHT*0.5) max_y = pos[1] + LiteGraph.NODE_SLOT_HEIGHT*0.5; - ctx.fillStyle = slot.links && slot.links.length ? (slot.colorOn || this.default_connection_color.output_on) : (slot.colorOff || this.default_connection_color.output_off); + ctx.fillStyle = slot.links && slot.links.length ? (slot.color_on || this.default_connection_color.output_on) : (slot.color_off || this.default_connection_color.output_off); ctx.beginPath(); //ctx.rect( node.size[0] - 14,i*14,10,10); @@ -5314,7 +5408,10 @@ LGraphCanvas.prototype.drawNode = function(node, ctx ) if(text) { ctx.fillStyle = LiteGraph.NODE_TEXT_COLOR; - ctx.fillText(text, pos[0] - 10,pos[1] + 5); + if( node.flags.horizontal || slot.dir == LiteGraph.DOWN ) + ctx.fillText(text,pos[0],pos[1] - 8); + else + ctx.fillText(text, pos[0] - 10,pos[1] + 5); } } } @@ -5343,7 +5440,7 @@ LGraphCanvas.prototype.drawNode = function(node, ctx ) var slot = node.inputs[i]; if( slot.link == null ) continue; - ctx.fillStyle = slot.colorOn || this.default_connection_color.input_on; + ctx.fillStyle = slot.color_on || this.default_connection_color.input_on; ctx.beginPath(); if ( slot.type === LiteGraph.EVENT || slot.shape === LiteGraph.BOX_SHAPE) { ctx.rect(0.5, 4 - LiteGraph.NODE_TITLE_HEIGHT + 0.5,14,LiteGraph.NODE_TITLE_HEIGHT - 8); @@ -5367,7 +5464,7 @@ LGraphCanvas.prototype.drawNode = function(node, ctx ) var slot = node.outputs[i]; if(!slot.links || !slot.links.length) continue; - ctx.fillStyle = slot.colorOn || this.default_connection_color.output_on; + ctx.fillStyle = slot.color_on || this.default_connection_color.output_on; ctx.strokeStyle = "black"; ctx.beginPath(); if (slot.type === LiteGraph.EVENT || slot.shape === LiteGraph.BOX_SHAPE) { @@ -5393,7 +5490,10 @@ LGraphCanvas.prototype.drawNode = function(node, ctx ) ctx.globalAlpha = 1.0; } -/* Renders the node shape */ +/** +* draws the shape of the given node in the canvas +* @method drawNodeShape +**/ LGraphCanvas.prototype.drawNodeShape = function( node, ctx, size, fgcolor, bgcolor, selected, mouse_over ) { //bg rect @@ -5547,6 +5647,7 @@ LGraphCanvas.prototype.drawNodeShape = function( node, ctx, size, fgcolor, bgcol areah += title_height; } ctx.lineWidth = 1; + ctx.globalAlpha = 0.8; ctx.beginPath(); if(shape == LiteGraph.BOX_SHAPE) ctx.rect(-6 + areax,-6 + areay, 12 + areaw, 12 + areah ); @@ -5556,13 +5657,18 @@ LGraphCanvas.prototype.drawNodeShape = function( node, ctx, size, fgcolor, bgcol ctx.roundRect(-6 + areax,-6 + areay, 12 + areaw, 12 + areah , this.round_radius * 2, 2); else if (shape == LiteGraph.CIRCLE_SHAPE) ctx.arc(size[0] * 0.5, size[1] * 0.5, size[0] * 0.5 + 6, 0, Math.PI*2); - ctx.strokeStyle = "#DDD"; + ctx.strokeStyle = "#FFF"; ctx.stroke(); ctx.strokeStyle = fgcolor; + ctx.globalAlpha = 1; } } -//OPTIMIZE THIS: precatch connections position instead of recomputing them every time +/** +* draws every connection visible in the canvas +* OPTIMIZE THIS: precatch connections position instead of recomputing them every time +* @method drawConnections +**/ LGraphCanvas.prototype.drawConnections = function(ctx) { var now = LiteGraph.getTime(); @@ -5598,22 +5704,31 @@ LGraphCanvas.prototype.drawConnections = function(ctx) start_node_slotpos = [start_node.pos[0] + 10, start_node.pos[1] + 10]; else start_node_slotpos = start_node.getConnectionPos(false, start_node_slot); + var end_node_slotpos = node.getConnectionPos(true,i); + var start_slot = start_node.outputs[start_node_slot]; + var end_slot = node.inputs[i]; + var start_dir = start_slot.dir || (start_node.flags.horizontal ? LiteGraph.DOWN : LiteGraph.RIGHT); + var end_dir = end_slot.dir || (node.flags.horizontal ? LiteGraph.UP : LiteGraph.LEFT); - this.renderLink( ctx, start_node_slotpos, node.getConnectionPos(true,i), link ); + this.renderLink( ctx, start_node_slotpos, end_node_slotpos, link, false, 0, null, start_dir, end_dir ); //event triggered rendered on top if(link && link._last_time && (now - link._last_time) < 1000 ) { var f = 2.0 - (now - link._last_time) * 0.002; var color = "rgba(255,255,255, " + f.toFixed(2) + ")"; - this.renderLink( ctx, start_node_slotpos, node.getConnectionPos(true,i), link, true, f, color ); + this.renderLink( ctx, start_node_slotpos, end_node_slotpos, link, true, f, color, start_dir, end_dir ); } } } ctx.globalAlpha = 1; } -LGraphCanvas.prototype.renderLink = function( ctx, a, b, link, skip_border, flow, color ) +/** +* draws a link between two points +* @method renderLink +**/ +LGraphCanvas.prototype.renderLink = function( ctx, a, b, link, skip_border, flow, color, start_dir, end_dir ) { if(!this.highquality_render) { @@ -5624,6 +5739,9 @@ LGraphCanvas.prototype.renderLink = function( ctx, a, b, link, skip_border, flow return; } + start_dir = start_dir || LiteGraph.RIGHT; + end_dir = end_dir || LiteGraph.LEFT; + var dist = distance(a,b); if(this.render_connections_border && this.scale > 0.6) @@ -5632,7 +5750,7 @@ LGraphCanvas.prototype.renderLink = function( ctx, a, b, link, skip_border, flow //choose color if( !color && link ) color = LGraphCanvas.link_type_colors[ link.type ]; - if(!color) + if( !color ) color = this.default_link_color; if( link != null && this.highlighted_links[ link.id ] ) @@ -5644,9 +5762,27 @@ LGraphCanvas.prototype.renderLink = function( ctx, a, b, link, skip_border, flow if(this.render_curved_connections) //splines { ctx.moveTo(a[0],a[1]); - ctx.bezierCurveTo(a[0] + dist*0.25, a[1], - b[0] - dist*0.25 , b[1], - b[0] ,b[1] ); + var start_offset_x = 0; + var start_offset_y = 0; + var end_offset_x = 0; + var end_offset_y = 0; + switch(start_dir) + { + case LiteGraph.LEFT: start_offset_x = dist*-0.25; break; + case LiteGraph.RIGHT: start_offset_x = dist*0.25; break; + case LiteGraph.UP: start_offset_y = dist*-0.25; break; + case LiteGraph.DOWN: start_offset_y = dist*0.25; break; + } + switch(end_dir) + { + case LiteGraph.LEFT: end_offset_x = dist*-0.25; break; + case LiteGraph.RIGHT: end_offset_x = dist*0.25; break; + case LiteGraph.UP: end_offset_y = dist*-0.25; break; + case LiteGraph.DOWN: end_offset_y = dist*0.25; break; + } + ctx.bezierCurveTo(a[0] + start_offset_x, a[1] + start_offset_y, + b[0] + end_offset_x , b[1] + end_offset_y, + b[0], b[1] ); } else //lines { @@ -5675,8 +5811,8 @@ LGraphCanvas.prototype.renderLink = function( ctx, a, b, link, skip_border, flow if(this.render_connection_arrows && this.scale > 0.6) { //compute two points in the connection - var pos = this.computeConnectionPoint(a,b,0.5); - var pos2 = this.computeConnectionPoint(a,b,0.51); + var pos = this.computeConnectionPoint(a, b, 0.5, start_dir, end_dir); + var pos2 = this.computeConnectionPoint(a, b, 0.51, start_dir, end_dir); //compute the angle between them so the arrow points in the right direction var angle = 0; @@ -5704,7 +5840,7 @@ LGraphCanvas.prototype.renderLink = function( ctx, a, b, link, skip_border, flow for(var i = 0; i < 5; ++i) { var f = (LiteGraph.getTime() * 0.001 + (i * 0.2)) % 1; - var pos = this.computeConnectionPoint(a,b,f); + var pos = this.computeConnectionPoint(a,b,f, start_dir, end_dir); ctx.beginPath(); ctx.arc(pos[0],pos[1],5,0,2*Math.PI); ctx.fill(); @@ -5712,14 +5848,32 @@ LGraphCanvas.prototype.renderLink = function( ctx, a, b, link, skip_border, flow } } -LGraphCanvas.prototype.computeConnectionPoint = function(a,b,t) +LGraphCanvas.prototype.computeConnectionPoint = function(a,b,t,start_dir,end_dir) { + start_dir = start_dir || LiteGraph.RIGHT; + end_dir = end_dir || LiteGraph.LEFT; + var dist = distance(a,b); var p0 = a; - var p1 = [ a[0] + dist*0.25, a[1] ]; - var p2 = [ b[0] - dist*0.25, b[1] ]; + var p1 = [ a[0], a[1] ]; + var p2 = [ b[0], b[1] ]; var p3 = b; + switch(start_dir) + { + case LiteGraph.LEFT: p1[0] += dist*-0.25; break; + case LiteGraph.RIGHT: p1[0] += dist*0.25; break; + case LiteGraph.UP: p1[1] += dist*-0.25; break; + case LiteGraph.DOWN: p1[1] += dist*0.25; break; + } + switch(end_dir) + { + case LiteGraph.LEFT: p2[0] += dist*-0.25; break; + case LiteGraph.RIGHT: p2[0] += dist*0.25; break; + case LiteGraph.UP: p2[1] += dist*-0.25; break; + case LiteGraph.DOWN: p2[1] += dist*0.25; break; + } + var c1 = (1-t)*(1-t)*(1-t); var c2 = 3*((1-t)*(1-t))*t; var c3 = 3*(1-t)*(t*t); @@ -5730,6 +5884,10 @@ LGraphCanvas.prototype.computeConnectionPoint = function(a,b,t) return [x,y]; } +/** +* draws the widgets stored inside a node +* @method drawNodeWidgets +**/ LGraphCanvas.prototype.drawNodeWidgets = function( node, posY, ctx, active_widget ) { if(!node.widgets || !node.widgets.length) @@ -5822,6 +5980,10 @@ LGraphCanvas.prototype.drawNodeWidgets = function( node, posY, ctx, active_widge ctx.textAlign = "left"; } +/** +* process an event on widgets +* @method processNodeWidgets +**/ LGraphCanvas.prototype.processNodeWidgets = function( node, pos, event, active_widget ) { if(!node.widgets || !node.widgets.length) @@ -5897,7 +6059,10 @@ LGraphCanvas.prototype.processNodeWidgets = function( node, pos, event, active_w return null; } - +/** +* draws every group area in the background +* @method drawGroups +**/ LGraphCanvas.prototype.drawGroups = function(canvas, ctx) { if(!this.graph) @@ -5939,7 +6104,10 @@ LGraphCanvas.prototype.drawGroups = function(canvas, ctx) ctx.restore(); } - +/** +* resizes the canvas to a given size, if no size is passed, then it tries to fill the parentNode +* @method resize +**/ LGraphCanvas.prototype.resize = function(width, height) { if(!width && !height) @@ -5959,7 +6127,11 @@ LGraphCanvas.prototype.resize = function(width, height) this.setDirty(true,true); } - +/** +* switches to live mode (node shapes are not rendered, only the content) +* this feature was designed when graphs where meant to create user interfaces +* @method switchLiveMode +**/ LGraphCanvas.prototype.switchLiveMode = function(transition) { if(!transition) @@ -6000,7 +6172,6 @@ LGraphCanvas.prototype.switchLiveMode = function(transition) LGraphCanvas.prototype.onNodeSelectionChange = function(node) { return; //disabled - //if(this.node_in_panel) this.showNodePanel(node); } LGraphCanvas.prototype.touchHandler = function(event)