diff --git a/build/litegraph.js b/build/litegraph.js index bc3bb7b10..5490634d1 100644 --- a/build/litegraph.js +++ b/build/litegraph.js @@ -4,6 +4,18 @@ // 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. * @@ -44,6 +56,7 @@ var LiteGraph = global.LiteGraph = { 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 @@ -65,9 +78,13 @@ var LiteGraph = global.LiteGraph = { console.log("Node registered: " + type); var categories = type.split("/"); + var classname = base_class.constructor.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 @@ -78,7 +95,7 @@ var LiteGraph = global.LiteGraph = { this.registered_node_types[ type ] = base_class; if(base_class.constructor.name) - this.Nodes[ base_class.constructor.name ] = base_class; + this.Nodes[ classname ] = base_class; //warnings if(base_class.prototype.onPropertyChange) @@ -323,6 +340,7 @@ LGraph.prototype.clear = function() 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; @@ -342,6 +360,8 @@ LGraph.prototype.clear = function() this.elapsed_time = 0.01; this.starttime = 0; + this.catch_errors = true; + //globals this.global_inputs = {}; this.global_outputs = {}; @@ -415,7 +435,7 @@ LGraph.prototype.start = function(interval) this.execution_timer_id = setInterval( function() { //execute - that.runStep(1); + that.runStep(1, !this.catch_errors ); },interval); } @@ -447,18 +467,18 @@ LGraph.prototype.stop = function() * @param {number} num number of steps to run, default is 1 */ -LGraph.prototype.runStep = function(num) +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_in_order ? this._nodes_in_order : this._nodes; + var nodes = this._nodes_executable ? this._nodes_executable : this._nodes; if(!nodes) return; - try + if( do_not_catch_errors ) { //iterations for(var i = 0; i < num; i++) @@ -477,16 +497,39 @@ LGraph.prototype.runStep = function(num) if( this.onAfterExecute ) this.onAfterExecute(); - this.errors_in_execution = false; } - catch (err) + else { - this.errors_in_execution = true; - if(LiteGraph.throw_errors) - throw err; - if(LiteGraph.debug) - console.log("Error during execution: " + err); - this.stop(); + 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; @@ -504,7 +547,11 @@ LGraph.prototype.runStep = function(num) */ LGraph.prototype.updateExecutionOrder = function() { - this._nodes_in_order = this.computeExecutionOrder( true ); + 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 @@ -1146,23 +1193,20 @@ LGraph.prototype.serialize = function() for(var i = 0, l = this._nodes.length; i < l; ++i) nodes_info.push( this._nodes[i].serialize() ); - //remove data from links, we dont want to store it + //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]; - link.data = null; - delete link._last_time; + links.push([ link.id, link.origin_id, link.origin_slot, link.target_id, link.target_slot ]); } var data = { -// graph: this.graph, - iteration: this.iteration, frame: this.frame, last_node_id: this.last_node_id, last_link_id: this.last_link_id, - links: LiteGraph.cloneObject( this.links ), - + links: links, //LiteGraph.cloneObject( this.links ), config: this.config, nodes: nodes_info }; @@ -1183,6 +1227,18 @@ LGraph.prototype.configure = function(data, keep_old) 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] }; + } + data.links = links; + } + //copy all stored fields for (var i in data) this[i] = data[i]; @@ -1287,7 +1343,7 @@ LGraph.prototype.onNodeTrace = function(node, msg, color) + 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) + + onConnectionsChange : a connection changed (new one or removed) (LiteGraph.INPUT or LiteGraph.OUTPUT, slot, true if connected, link_info, input_info ) */ /** @@ -1386,17 +1442,19 @@ LGraphNode.prototype.configure = function(info) { var input = this.inputs[i]; var link_info = this.graph.links[ input.link ]; - this.onConnectionsChange( LiteGraph.INPUT, i, true, link_info ); //link_info has been created now, so its updated + 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 ); //link_info has been created now, so its updated + this.onConnectionsChange( LiteGraph.OUTPUT, i, true, link_info, output ); //link_info has been created now, so its updated } } } @@ -1411,7 +1469,13 @@ LGraphNode.prototype.configure = function(info) 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] }; + 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) { @@ -1438,6 +1502,12 @@ LGraphNode.prototype.configure = function(info) LGraphNode.prototype.serialize = function() { + //clear outputs last data (because data in connections is never serialized but stored inside the outputs info) + if(this.outputs) + for(var i = 0; i < this.outputs.length; i++) + delete this.outputs[i]._data; + + //create serialization object var o = { id: this.id, title: this.title, @@ -1532,12 +1602,23 @@ LGraphNode.prototype.getTitle = function() * @param {number} slot * @param {*} data */ -LGraphNode.prototype.setOutputData = function(slot,data) +LGraphNode.prototype.setOutputData = function(slot, data) { if(!this.outputs) return; - if(slot > -1 && slot < this.outputs.length && this.outputs[slot] && this.outputs[slot].links != null) + 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++) { @@ -1598,7 +1679,7 @@ LGraphNode.prototype.isInputConnected = function(slot) * tells you info about an input connection (which node, type, etc) * @method getInputInfo * @param {number} slot -* @return {Object} object or null +* @return {Object} object or null { link: id, name: string, type: string or 0 } */ LGraphNode.prototype.getInputInfo = function(slot) { @@ -1630,12 +1711,29 @@ LGraphNode.prototype.getInputNode = function( slot ) return this.graph.getNodeById( link_info.origin_id ); } +/** +* 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 +* @return {Object} object or null { name: string, type: string, links: [ ids of links in number ] } */ LGraphNode.prototype.getOutputInfo = function(slot) { @@ -2039,7 +2137,8 @@ LGraphNode.prototype.getSlotInPosition = function( x, y ) */ LGraphNode.prototype.findInputSlot = function(name) { - if(!this.inputs) return -1; + if(!this.inputs) + return -1; for(var i = 0, l = this.inputs.length; i < l; ++i) if(name == this.inputs[i].name) return i; @@ -2146,10 +2245,11 @@ LGraphNode.prototype.connect = function( slot, target_node, target_slot ) var input = target_node.inputs[target_slot]; - if( LiteGraph.isValidConnection( output.type, input.type) ) + if( LiteGraph.isValidConnection( output.type, input.type ) ) { var link_info = { - id: this.graph.last_link_id++, + id: this.graph.last_link_id++, + type: input.type, origin_id: this.id, origin_slot: slot, target_id: target_node.id, @@ -2167,9 +2267,9 @@ LGraphNode.prototype.connect = function( slot, target_node, target_slot ) target_node.inputs[target_slot].link = link_info.id; if(this.onConnectionsChange) - this.onConnectionsChange( LiteGraph.OUTPUT, slot, true, link_info ); //link_info has been created now, so its updated + 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 ); + target_node.onConnectionsChange( LiteGraph.INPUT, target_slot, true, link_info, input ); } this.setDirtyCanvas(false,true); @@ -2226,12 +2326,13 @@ LGraphNode.prototype.disconnectOutput = function( slot, target_node ) if( link_info.target_id == target_node.id ) { output.links.splice(i,1); //remove here - target_node.inputs[ link_info.target_slot ].link = null; //remove there + 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 ); //link_info hasnt been modified so its ok + 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 ); + this.onConnectionsChange( LiteGraph.OUTPUT, slot, false, link_info, output ); break; } } @@ -2244,13 +2345,17 @@ LGraphNode.prototype.disconnectOutput = function( slot, target_node ) var link_info = this.graph.links[ link_id ]; var target_node = this.graph.getNodeById( link_info.target_id ); + var input = null; if(target_node) - target_node.inputs[ link_info.target_slot ].link = null; //remove other side link + { + input = target_node.inputs[ link_info.target_slot ]; + input.link = null; //remove other side link + } 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 ); //link_info hasnt been modified so its ok + 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 ); + this.onConnectionsChange( LiteGraph.OUTPUT, slot, false, link_info, output ); } output.links = null; } @@ -2319,9 +2424,9 @@ LGraphNode.prototype.disconnectInput = function( slot ) } if( this.onConnectionsChange ) - this.onConnectionsChange( LiteGraph.INPUT, slot, false, link_info ); + this.onConnectionsChange( LiteGraph.INPUT, slot, false, link_info, input ); if( target_node.onConnectionsChange ) - target_node.onConnectionsChange( LiteGraph.OUTPUT, i, false, link_info ); + target_node.onConnectionsChange( LiteGraph.OUTPUT, i, false, link_info, output ); } this.setDirtyCanvas(false,true); @@ -2507,7 +2612,7 @@ LGraphNode.prototype.localToScreen = function(x,y, graphcanvas) * * @class LGraphCanvas * @constructor -* @param {HTMLCanvas} canvas the canvas where you want to render (it accepts a selector in string format or the canvas itself) +* @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 } */ @@ -2547,7 +2652,7 @@ global.LGraphCanvas = LiteGraph.LGraphCanvas = function LGraphCanvas( canvas, gr this.render_curved_connections = true; this.render_connection_arrows = true; - this.connections_width = 4; + this.connections_width = 3; //link canvas and graph if(graph) @@ -2677,7 +2782,7 @@ LGraphCanvas.prototype.closeSubgraph = function() * assigns a canvas * * @method setCanvas -* @param {Canvas} assigns a canvas +* @param {Canvas} assigns a canvas (also accepts the ID of the element (not a selector) */ LGraphCanvas.prototype.setCanvas = function( canvas, skip_events ) { @@ -2723,6 +2828,8 @@ LGraphCanvas.prototype.setCanvas = function( canvas, skip_events ) 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"); } @@ -2984,6 +3091,7 @@ LGraphCanvas.prototype.processMouseDown = function(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 ); @@ -3165,6 +3273,7 @@ LGraphCanvas.prototype.processMouseMove = function(e) 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]]; @@ -3312,6 +3421,7 @@ LGraphCanvas.prototype.processMouseUp = function(e) 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 ); @@ -3929,28 +4039,21 @@ LGraphCanvas.prototype.drawFrontCanvas = function() default: link_color = "#AFA"; } - this.renderLink(ctx, this.connecting_pos, [this.canvas_mouse[0],this.canvas_mouse[1]], 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 ); 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); - - /* - if( this.connecting_output.round) - ctx.arc( this.connecting_pos[0], this.connecting_pos[1],4,0,Math.PI*2); - else - ctx.rect( this.connecting_pos[0], this.connecting_pos[1],12,6); - */ + 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.arc( this._highlight_input[0], this._highlight_input[1],6,0,Math.PI*2); ctx.fill(); } } @@ -4494,22 +4597,21 @@ LGraphCanvas.prototype.drawConnections = function(ctx) else start_node_slotpos = start_node.getConnectionPos(false, start_node_slot); - var color = LGraphCanvas.link_type_colors[ node.inputs[i].type ] || this.default_link_color; + this.renderLink( ctx, start_node_slotpos, node.getConnectionPos(true,i), link ); - this.renderLink(ctx, start_node_slotpos, node.getConnectionPos(true,i), color ); - - if(link && link._last_time && now - link._last_time < 1000 ) + //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) , color, true, f ); + this.renderLink( ctx, start_node_slotpos, node.getConnectionPos(true,i), link, true, f, color ); } } } ctx.globalAlpha = 1; } -LGraphCanvas.prototype.renderLink = function(ctx,a,b,color, skip_border, flow ) +LGraphCanvas.prototype.renderLink = function( ctx, a, b, link, skip_border, flow, color ) { if(!this.highquality_render) { @@ -4525,6 +4627,13 @@ LGraphCanvas.prototype.renderLink = function(ctx,a,b,color, skip_border, flow ) 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; + + //begin line shape ctx.beginPath(); if(this.render_curved_connections) //splines @@ -4542,6 +4651,7 @@ LGraphCanvas.prototype.renderLink = function(ctx,a,b,color, skip_border, flow ) 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)"; @@ -4551,35 +4661,39 @@ LGraphCanvas.prototype.renderLink = function(ctx,a,b,color, skip_border, flow ) ctx.lineWidth = this.connections_width; ctx.fillStyle = ctx.strokeStyle = color; ctx.stroke(); + //end line shape - //no symbols - if(!this.render_connection_arrows || this.scale < 0.6) - return; - - //render arrow - if(this.render_connection_arrows && this.scale > 0.6) + //render arrow in the middle + if( this.render_connection_arrows && this.scale >= 0.6 ) { - var pos = this.computeConnectionPoint(a,b,0.5); - var pos2 = this.computeConnectionPoint(a,b,0.51); + //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); - //get two points in the bezier curve - 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; + //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; - 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 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) @@ -4720,19 +4834,20 @@ LGraphCanvas.prototype.touchHandler = function(event) /* CONTEXT MENU ********************/ -LGraphCanvas.onMenuAdd = function(node, e, prev_menu, canvas, first_event ) +LGraphCanvas.onMenuAdd = function( node, options, e, prev_menu ) { + var canvas = LGraphCanvas.active_canvas; var ref_window = canvas.getCanvasWindow(); var values = LiteGraph.getNodeTypesCategories(); - var entries = {}; + var entries = []; for(var i in values) if(values[i]) - entries[ i ] = { value: values[i], content: values[i] , is_menu: true }; + entries.push({ value: values[i], content: values[i], has_submenu: true }); - var menu = LiteGraph.createContextMenu(entries, {event: e, callback: inner_clicked, from: prev_menu}, ref_window); + var menu = new LiteGraph.ContextMenu( entries, { event: e, callback: inner_clicked, parentMenu: prev_menu }, ref_window); - function inner_clicked(v, e) + function inner_clicked( v, option, e ) { var category = v.value; var node_types = LiteGraph.getNodeTypesInCategory(category); @@ -4740,16 +4855,17 @@ LGraphCanvas.onMenuAdd = function(node, e, prev_menu, canvas, first_event ) for(var i in node_types) values.push( { content: node_types[i].title, value: node_types[i].type }); - LiteGraph.createContextMenu(values, {event: e, callback: inner_create, from: menu}, ref_window); + new LiteGraph.ContextMenu( values, {event: e, callback: inner_create, parentMenu: menu }, ref_window); return false; } - function inner_create(v, e) + 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); + node.pos = canvas.convertEventToCanvas( first_event ); canvas.graph.add( node ); } } @@ -4768,13 +4884,14 @@ LGraphCanvas.onMenuNodeEdit = function() } -LGraphCanvas.showMenuNodeInputs = function(node, e, prev_menu) +LGraphCanvas.showMenuNodeOptionalInputs = function( v, options, e, prev_menu, node ) { if(!node) return; var that = this; - var ref_window = this.getCanvasWindow(); + var canvas = LGraphCanvas.active_canvas; + var ref_window = canvas.getCanvasWindow(); var options = node.optional_inputs; if(node.onGetInputs) @@ -4802,7 +4919,7 @@ LGraphCanvas.showMenuNodeInputs = function(node, e, prev_menu) if(!entries.length) return; - var menu = LiteGraph.createContextMenu(entries, {event: e, callback: inner_clicked, from: prev_menu}, ref_window); + var menu = new LiteGraph.ContextMenu(entries, { event: e, callback: inner_clicked, parentMenu: prev_menu, node: node }, ref_window); function inner_clicked(v, e, prev) { @@ -4810,7 +4927,7 @@ LGraphCanvas.showMenuNodeInputs = function(node, e, prev_menu) return; if(v.callback) - v.callback.call(that, node, v, e, prev); + v.callback.call( that, node, v, e, prev ); if(v.value) node.addInput(v.value[0],v.value[1], v.value[2]); @@ -4819,13 +4936,14 @@ LGraphCanvas.showMenuNodeInputs = function(node, e, prev_menu) return false; } -LGraphCanvas.showMenuNodeOutputs = function(node, e, prev_menu) +LGraphCanvas.showMenuNodeOptionalOutputs = function( v, options, e, prev_menu, node ) { if(!node) return; var that = this; - var ref_window = this.getCanvasWindow(); + var canvas = LGraphCanvas.active_canvas; + var ref_window = canvas.getCanvasWindow(); var options = node.optional_outputs; if(node.onGetOutputs) @@ -4859,7 +4977,7 @@ LGraphCanvas.showMenuNodeOutputs = function(node, e, prev_menu) if(!entries.length) return; - var menu = LiteGraph.createContextMenu(entries, {event: e, callback: inner_clicked, from: prev_menu}, ref_window); + var menu = new LiteGraph.ContextMenu(entries, {event: e, callback: inner_clicked, parentMenu: prev_menu, node: node }, ref_window); function inner_clicked( v, e, prev ) { @@ -4867,7 +4985,7 @@ LGraphCanvas.showMenuNodeOutputs = function(node, e, prev_menu) return; if(v.callback) - v.callback.call(that, node, v, e, prev); + v.callback.call( that, node, v, e, prev ); if(!v.value) return; @@ -4878,24 +4996,25 @@ LGraphCanvas.showMenuNodeOutputs = function(node, e, prev_menu) { var entries = []; for(var i in value) - entries.push({content: i, value: value[i]}); - LiteGraph.createContextMenu(entries, {event: e, callback: inner_clicked, from: prev_menu}); + 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.addOutput( v.value[0], v.value[1], v.value[2]); } return false; } -LGraphCanvas.onShowMenuNodeProperties = function(node,e, prev_menu) +LGraphCanvas.onShowMenuNodeProperties = function( value, options, e, prev_menu, node ) { if(!node || !node.properties) return; var that = this; - var ref_window = this.getCanvasWindow(); + var canvas = LGraphCanvas.active_canvas; + var ref_window = canvas.getCanvasWindow(); var entries = []; for (var i in node.properties) @@ -4908,13 +5027,14 @@ LGraphCanvas.onShowMenuNodeProperties = function(node,e, prev_menu) if(!entries.length) return; - var menu = LiteGraph.createContextMenu(entries, {event: e, callback: inner_clicked, from: prev_menu, allow_html: true },ref_window); + 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, e, prev ) + function inner_clicked( v, options, e, prev ) { if(!node) return; - that.showEditPropertyValue( node, v.value, { event: e }); + var rect = this.getBoundingClientRect(); + canvas.showEditPropertyValue( node, v.value, { position: [rect.left, rect.top] }); } return false; @@ -4927,7 +5047,16 @@ LGraphCanvas.decodeHTML = function( str ) return e.innerHTML; } -LGraphCanvas.onShowTitleEditor = function( node, event ) +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 = ""; @@ -4947,7 +5076,10 @@ LGraphCanvas.onShowTitleEditor = function( node, event ) }); } - var rect = this.canvas.getClientRects()[0]; + var graphcanvas = LGraphCanvas.active_canvas; + var canvas = graphcanvas.canvas; + + var rect = canvas.getBoundingClientRect(); var offsetx = -20; var offsety = -20; if(rect) @@ -4963,13 +5095,13 @@ LGraphCanvas.onShowTitleEditor = function( node, event ) } else { - dialog.style.left = (this.canvas.width * 0.5 + offsetx) + "px"; - dialog.style.top = (this.canvas.height * 0.5 + offsety) + "px"; + 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 ); - this.canvas.parentNode.appendChild( dialog ); + canvas.parentNode.appendChild( dialog ); function inner() { @@ -5083,16 +5215,24 @@ LGraphCanvas.prototype.showEditPropertyValue = function( node, property, options offsety -= rect.top; } - if( options.event ) + if( options.position ) { - dialog.style.left = (options.event.pageX + offsetx) + "px"; - dialog.style.top = (options.event.pageY + offsety)+ "px"; - } - else + offsetx += options.position[0]; + offsety += options.position[1]; + } + else if( options.event ) { - dialog.style.left = (this.canvas.width * 0.5 + offsetx) + "px"; - dialog.style.top = (this.canvas.height * 0.5 + offsety) + "px"; + 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"; var button = dialog.querySelector("button"); button.addEventListener("click", inner ); @@ -5118,20 +5258,20 @@ LGraphCanvas.prototype.showEditPropertyValue = function( node, property, options } } -LGraphCanvas.onMenuNodeCollapse = function(node) +LGraphCanvas.onMenuNodeCollapse = function( value, options, e, menu, node ) { node.flags.collapsed = !node.flags.collapsed; node.setDirtyCanvas(true,true); } -LGraphCanvas.onMenuNodePin = function(node) +LGraphCanvas.onMenuNodePin = function( value, options, e, menu, node ) { node.pin(); } -LGraphCanvas.onMenuNodeMode = function(node, e, prev_menu) +LGraphCanvas.onMenuNodeMode = function( value, options, e, menu, node ) { - LiteGraph.createContextMenu(["Always","On Event","Never"], {event: e, callback: inner_clicked, from: prev_menu}); + new LiteGraph.ContextMenu(["Always","On Event","Never"], {event: e, callback: inner_clicked, parentMenu: prev_menu, node: node }); function inner_clicked(v) { @@ -5150,8 +5290,11 @@ LGraphCanvas.onMenuNodeMode = function(node, e, prev_menu) return false; } -LGraphCanvas.onMenuNodeColors = function(node, e, prev_menu) +LGraphCanvas.onMenuNodeColors = function( value, options, e, menu, node ) { + if(!node) + throw("no node for color"); + var values = []; for(var i in LGraphCanvas.node_colors) { @@ -5159,12 +5302,14 @@ LGraphCanvas.onMenuNodeColors = function(node, e, prev_menu) var value = {value:i, content:""+i+""}; values.push(value); } - LiteGraph.createContextMenu(values, {event: e, callback: inner_clicked, from: prev_menu}); + 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(!node) + return; + + var color = LGraphCanvas.node_colors[ v.value ]; if(color) { node.color = color.color; @@ -5176,13 +5321,17 @@ LGraphCanvas.onMenuNodeColors = function(node, e, prev_menu) return false; } -LGraphCanvas.onMenuNodeShapes = function(node,e) +LGraphCanvas.onMenuNodeShapes = function( value, options, e, menu, node ) { - LiteGraph.createContextMenu(["box","round"], {event: e, callback: inner_clicked}); + if(!node) + throw("no node passed"); + + new LiteGraph.ContextMenu(["box","round"], { event: e, callback: inner_clicked, parentMenu: menu, node: node }); function inner_clicked(v) { - if(!node) return; + if(!node) + return; node.shape = v; node.setDirtyCanvas(true); } @@ -5190,14 +5339,18 @@ LGraphCanvas.onMenuNodeShapes = function(node,e) return false; } -LGraphCanvas.onMenuNodeRemove = function(node) +LGraphCanvas.onMenuNodeRemove = function( value, options, e, menu, node ) { - if(node.removable == false) return; + if(!node) + throw("no node passed"); + + if(node.removable == false) + return; node.graph.remove(node); node.setDirtyCanvas(true,true); } -LGraphCanvas.onMenuNodeClone = function(node) +LGraphCanvas.onMenuNodeClone = function( value, options, e, menu, node ) { if(node.clonable == false) return; var newnode = node.clone(); @@ -5222,7 +5375,7 @@ LGraphCanvas.prototype.getCanvasMenuOptions = function() else { options = [ - {content:"Add Node", is_menu: true, callback: LGraphCanvas.onMenuAdd } + { content:"Add Node", has_submenu: true, callback: LGraphCanvas.onMenuAdd } //{content:"Collapse All", callback: LGraphCanvas.onMenuCollapseAll } ]; @@ -5240,7 +5393,8 @@ LGraphCanvas.prototype.getCanvasMenuOptions = function() return options; } -LGraphCanvas.prototype.getNodeMenuOptions = function(node) +//called by processContextMenu to extract the menu list +LGraphCanvas.prototype.getNodeMenuOptions = function( node ) { var options = null; @@ -5248,17 +5402,18 @@ LGraphCanvas.prototype.getNodeMenuOptions = function(node) options = node.getMenuOptions(this); else options = [ - {content:"Inputs", is_menu: true, disabled:true, callback: LGraphCanvas.showMenuNodeInputs }, - {content:"Outputs", is_menu: true, disabled:true, callback: LGraphCanvas.showMenuNodeOutputs }, + {content:"Inputs", has_submenu: true, disabled:true, callback: LGraphCanvas.showMenuNodeOptionalInputs }, + {content:"Outputs", has_submenu: true, disabled:true, callback: LGraphCanvas.showMenuNodeOptionalOutputs }, null, - {content:"Properties", is_menu: true, callback: LGraphCanvas.onShowMenuNodeProperties }, + {content:"Properties", has_submenu: true, callback: LGraphCanvas.onShowMenuNodeProperties }, null, {content:"Title", callback: LGraphCanvas.onShowTitleEditor }, - {content:"Mode", is_menu: true, callback: LGraphCanvas.onMenuNodeMode }, + {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", is_menu: true, callback: LGraphCanvas.onMenuNodeColors }, - {content:"Shapes", is_menu: true, callback: LGraphCanvas.onMenuNodeShapes }, + {content:"Colors", has_submenu: true, callback: LGraphCanvas.onMenuNodeColors }, + {content:"Shapes", has_submenu: true, callback: LGraphCanvas.onMenuNodeShapes }, null ]; @@ -5291,21 +5446,28 @@ LGraphCanvas.prototype.getNodeMenuOptions = function(node) options[1].disabled = false; } + if(node.graph && node.graph.onGetNodeMenuOptions ) + node.graph.onGetNodeMenuOptions( options, node ); + return options; } -LGraphCanvas.prototype.processContextMenu = function(node, event) +LGraphCanvas.prototype.processContextMenu = function( node, event ) { var that = this; - var win = this.getCanvasWindow(); + var canvas = LGraphCanvas.active_canvas; + var ref_window = canvas.getCanvasWindow(); var menu_info = null; - var options = {event: event, callback: inner_option_clicked}; + 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) { @@ -5317,14 +5479,13 @@ LGraphCanvas.prototype.processContextMenu = function(node, event) else menu_info = node ? this.getNodeMenuOptions(node) : this.getCanvasMenuOptions(); - //show menu if(!menu_info) return; - var menu = LiteGraph.createContextMenu( menu_info, options, win); + var menu = new LiteGraph.ContextMenu( menu_info, options, ref_window ); - function inner_option_clicked(v,e) + function inner_option_clicked( v, options, e ) { if(!v) return; @@ -5338,8 +5499,8 @@ LGraphCanvas.prototype.processContextMenu = function(node, event) return; } - if(v.callback) - return v.callback.call(that, node, e, menu, that, event ); + //if(v.callback) + // return v.callback.call(that, node, options, e, menu, that, event ); } } @@ -5479,195 +5640,332 @@ function num2hex(triplet) { LiteGraph.num2hex = num2hex; -/* LiteGraph GUI elements *************************************/ +/* LiteGraph GUI elements used for canvas editing *************************************/ -LiteGraph.createContextMenu = function(values, options, ref_window) +/** +* 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; - //allows to create graph canvas in separate window - ref_window = ref_window || window; + //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); - if (!options.from) - LiteGraph.closeAllContextMenus( ref_window ); - else { - //closing submenus - var menus = document.querySelectorAll(".graphcontextmenu"); - for (var key in menus) { - if (menus[key].previousSibling == options.from) - menus[key].closeMenu(); - } - } - var root = ref_window.document.createElement("div"); - root.className = "graphcontextmenu graphmenubar-panel"; this.root = root; - var style = root.style; - - style.minWidth = "100px"; - style.minHeight = "20px"; - - style.position = "fixed"; - style.top = "100px"; - style.left = "100px"; - style.color = "#AAF"; - style.padding = "2px"; - style.borderBottom = "2px solid #AAF"; - style.backgroundColor = "#444"; - style.zIndex = 10; //title if(options.title) { var element = document.createElement("div"); - element.className = "graphcontextmenu-title"; + element.className = "litemenu-title"; element.innerHTML = options.title; root.appendChild(element); } - //avoid a context menu in a context menu - root.addEventListener("contextmenu", function(e) { e.preventDefault(); return false; }); - + //entries + var num = 0; for(var i in values) { - var item = values[i]; - var element = ref_window.document.createElement("div"); - element.className = "graphmenu-entry"; - - if(item == null) - { - element.className += " separator"; - root.appendChild(element); - continue; - } - - if(item.is_menu) - element.className += " submenu"; - - if(item.disabled) - element.className += " disabled"; - - if(item.className) - element.className += " " + item.className; - - element.style.cursor = "pointer"; - element.dataset["value"] = typeof(item) == "string" ? item : item.value; - element.data = item; - - var content = ""; - if(typeof(item) == "string") - content = values.constructor == Array ? values[i] : i; - else - content = item.content ? item.content : i; - if(options.allow_html) - element.innerHTML = content; - else - element.innerText = content; - - element.addEventListener("click", on_click ); - root.appendChild(element); + 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++; } - root.addEventListener("mouseover", function(e) { - this.mouse_inside = true; - }); - - root.addEventListener("mouseout", function(e) { - //console.log("OUT!"); - //check if mouse leave a inner element - var aux = e.relatedTarget || e.toElement; - while(aux != this && aux != ref_window.document) - aux = aux.parentNode; - - if(aux == this) + //close on leave + root.addEventListener("mouseleave", function(e) { + if(that.lock) return; - this.mouse_inside = false; - if(!this.block_close) - this.closeMenu(); + that.close(e); }); //insert before checking position - ref_window.document.body.appendChild(root); + var root_document = document; + if(options.event) + root_document = options.event.target.ownerDocument; - var root_rect = root.getClientRects()[0]; - - //link menus - if(options.from) - { - options.from.block_close = true; - } + 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.left) - left = options.left; + if(options.title) + top -= 20; - var rect = ref_window.document.body.getClientRects()[0]; - - if(options.from) + if(options.parentMenu) { - var parent_rect = options.from.getClientRects()[0]; - left = parent_rect.left + parent_rect.width; + var rect = options.parentMenu.root.getBoundingClientRect(); + left = rect.left + rect.width; } - - if(left > (rect.width - root_rect.width - 10)) - left = (rect.width - root_rect.width - 10); - if(top > (rect.height - root_rect.height - 10)) - top = (rect.height - root_rect.height - 10); + 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"; +} - function on_click(e) { - var value = this.dataset["value"]; - var close = true; - if(options.callback) +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) { - var ret = options.callback.call(root, this.data, e ); - if( ret !== undefined ) close = ret; + if(value.disabled) + { + disabled = true; + element.classList.add("disabled"); + } + if(value.submenu || value.has_submenu) + element.classList.add("has_submenu"); } - if(close) - LiteGraph.closeAllContextMenus( ref_window ); - //root.closeMenu(); + if(typeof(value) == "function") + { + element.dataset["value"] = name; + element.onclick_callback = value; + } + else + element.dataset["value"] = value; } - root.closeMenu = function() - { - if(options.from) - { - options.from.block_close = false; - if(!options.from.mouse_inside) - options.from.closeMenu(); - } - if(this.parentNode) - ref_window.document.body.removeChild(this); - }; + this.root.appendChild(element); + if(!disabled) + element.addEventListener("click", inner_onclick); + if(options.autoopen) + element.addEventListener("mouseenter", inner_over); - return root; + 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(".graphcontextmenu"); - if(!elements.length) return; + 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].parentNode) + { + if(result[i].close) + result[i].close(); + else if(result[i].parentNode) result[i].parentNode.removeChild( result[i] ); + } } LiteGraph.extendClass = function ( target, origin ) @@ -6110,7 +6408,7 @@ Console.prototype.onAction = function(action, param) Console.prototype.onExecute = function() { - var msg = this.getInputData(0); + var msg = this.getInputData(1); if(msg !== null) this.properties.msg = msg; console.log(msg); @@ -6124,6 +6422,64 @@ Console.prototype.onGetInputs = function() 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 ); + + + })(); //event related nodes (function(){ @@ -9216,17 +9572,19 @@ if(typeof(LiteGraph) != "undefined") 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) + if(!ctx.webgl && !LGraphTexturePreview.allow_preview) return; //not working well var tex = this.getInputData(0); - if(!tex) return; + if(!tex) + return; var tex_canvas = null; @@ -9263,12 +9621,19 @@ if(typeof(LiteGraph) != "undefined") LGraphTextureSave.prototype.onExecute = function() { var tex = this.getInputData(0); - if(!tex) return; + if(!tex) + return; if(this.properties.name) { - var container = LGraphTexture.getTexturesContainer(); - container[ this.properties.name ] = tex; + //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); @@ -9474,7 +9839,7 @@ if(typeof(LiteGraph) != "undefined") this.addOutput("Texture","Texture"); this.properties = {code:"", width: 512, height: 512}; - this.properties.code = "\nvoid main() {\n vec2 uv = coord;\n vec3 color = vec3(0.0);\n//your code here\n\ngl_FragColor = vec4(color, 1.0);\n}\n"; + 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"; } LGraphTextureShader.title = "Shader"; @@ -9484,65 +9849,133 @@ if(typeof(LiteGraph) != "undefined") "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 - //replug - if(this._shader_code != this.properties.code) - { - 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; - } - else - this.boxcolor = "green"; - /* - var uniforms = this._shader.uniformLocations; - //disconnect inputs - if(this.inputs) - for(var i = 0; i < this.inputs.length; i++) - { - var slot = this.inputs[i]; - if(slot.link != null) - this.disconnectInput(i); - } + var shader = this.getShader(); + if(!shader) + return; - for(var i = 0; i < uniforms.length; i++) + var tex_slot = 0; + + //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) { - var type = "number"; - if( this._shader.isSampler[i] ) - type = "texture"; - else - { - var v = gl.getUniform(this._shader.program, i); - type = typeof(v); - if(type == "object" && v.length) - { - switch(v.length) - { - 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; - } - } - } - this.addInput(i,type); + data.bind(slot); + data = slot; + slot++; } - */ + shader.setUniform( info.name, data ); } + //render to texture if(!this._tex || this._tex.width != this.properties.width || this._tex.height != this.properties.height ) this._tex = new GL.Texture( this.properties.width, this.properties.height, { format: gl.RGBA, filter: gl.LINEAR }); var tex = this._tex; - var shader = this._shader; var time = this.graph.getTime(); tex.drawTo(function() { shader.uniforms({texSize: [tex.width, tex.height], time: time}).draw( Mesh.getScreenQuad() ); @@ -9740,7 +10173,7 @@ if(typeof(LiteGraph) != "undefined") \n\ void main() {\n\ vec2 uv = v_coord;\n\ - uv += texture2D(u_textureB, uv).rg * u_factor;\n\ + uv += ( texture2D(u_textureB, uv).rg - vec2(0.5)) * u_factor;\n\ gl_FragColor = texture2D(u_texture, uv);\n\ }\n\ "; @@ -9956,16 +10389,127 @@ if(typeof(LiteGraph) != "undefined") 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("","Texture"); - this.properties = { low_precision: false }; + this.properties = { mipmap_offset: 0, low_precision: false }; + + this._uniforms = { u_texture: 0, u_mipmap_offset: this.properties.mipmap_offset }; } LGraphTextureAverage.title = "Average"; - LGraphTextureAverage.desc = "Compute the total average of a texture and stores it as a 1x1 pixel texture"; + LGraphTextureAverage.desc = "Compute a partial average (32 random samples) of a texture and stores it as a 1x1 pixel texture"; LGraphTextureAverage.prototype.onExecute = function() { @@ -9978,7 +10522,8 @@ if(typeof(LiteGraph) != "undefined") if(!LGraphTextureAverage._shader) { - LGraphTextureAverage._shader = new GL.Shader(Shader.SCREEN_VERTEX_SHADER, LGraphTextureAverage.pixel_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(); @@ -9991,8 +10536,10 @@ if(typeof(LiteGraph) != "undefined") 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,{u_texture:0}); + tex.toViewport( shader, uniforms ); }); this.setOutputData(0,this._temp_texture); @@ -10003,6 +10550,7 @@ if(typeof(LiteGraph) != "undefined") 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\ @@ -10010,8 +10558,8 @@ if(typeof(LiteGraph) != "undefined") 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] ) );\n\ - color += texture2D(u_texture, vec2( 1.0 - u_samples_a[i][j], u_samples_b[i][j] ) );\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\ @@ -11103,6 +11651,8 @@ LGraphTextureKuwaharaFilter.pixel_shader = "\n\ this.size = [LGraphTexture.image_preview_size, LGraphTexture.image_preview_size]; } + LGraphCubemap.title = "Cubemap"; + LGraphCubemap.prototype.onDropFile = function(data, filename, file) { if(!data) @@ -11777,6 +12327,15 @@ 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() @@ -11904,9 +12463,18 @@ function MIDIInterface( on_ready, on_error ) 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) @@ -11925,8 +12493,41 @@ 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 + "'" ); @@ -11939,12 +12540,15 @@ MIDIInterface.prototype.updatePorts = function() 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) @@ -11952,14 +12556,17 @@ MIDIInterface.prototype.onMIDIFailure = function(msg) console.error( "Failed to get MIDI access - " + msg ); } -MIDIInterface.prototype.openInputPort = function( port, callback) +MIDIInterface.prototype.openInputPort = function( port, callback ) { - var input_port = this.input_ports.get( port ); + 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) @@ -11974,15 +12581,27 @@ 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(port); + 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 @@ -12024,7 +12643,7 @@ LGMIDIIn.prototype.getPropertyInfo = function(name) var values = {}; for (var i = 0; i < this._midi.input_ports.size; ++i) { - var input = this._midi.input_ports.get(i); + var input = this._midi.input_ports.get( "input-" + i); values[i] = i + ".- " + input.name + " version:" + input.version; } return { type: "enum", values: values }; @@ -12067,6 +12686,7 @@ LGMIDIIn.prototype.onExecute = function() var v = null; switch (output.name) { + case "midi": v = this._midi; break; case "last_midi": v = last; break; default: continue; @@ -12281,8 +12901,10 @@ LGMIDIEvent.prototype.onExecute = function() v.channel = props.channel; break; case "command": v = props.cmd; 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 "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: @@ -12308,6 +12930,8 @@ LGMIDIEvent.prototype.onGetOutputs = function() { ["command","number"], ["note","number"], ["velocity","number"], + ["cc","number"], + ["cc_value","number"], ["pitch","number"], ["pitchbend","number"] ]; @@ -12317,6 +12941,31 @@ LGMIDIEvent.prototype.onGetOutputs = function() { 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() } @@ -12353,8 +13002,8 @@ LGAudio.getAudioContext = function() } //in case it crashes - if(this._audio_context.state == "suspended") - this._audio_context.resume(); + //if(this._audio_context.state == "suspended") + // this._audio_context.resume(); return this._audio_context; } @@ -12484,8 +13133,13 @@ LGAudio.onConnectionsChange = function( connection, slot, connected, link_info ) //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; @@ -12501,7 +13155,7 @@ LGAudio.createAudioNodeWrapper = function( class_object ) 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 ) @@ -12513,6 +13167,9 @@ LGAudio.loadSound = function( url, on_complete, on_error ) return; } + if( LGAudio.onProcessAudioURL ) + url = LGAudio.onProcessAudioURL( url ); + //load new sample var request = new XMLHttpRequest(); request.open('GET', url, true); @@ -12556,8 +13213,9 @@ function LGAudioSource() }; this._loading_audio = false; - this._audio_buffer = null; + 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" ); @@ -12565,7 +13223,7 @@ function LGAudioSource() //init context var context = LGAudio.getAudioContext(); - //create gain node + //create gain node to control volume this.audionode = context.createGain(); this.audionode.graphnode = this; this.audionode.gain.value = this.properties.gain; @@ -12575,6 +13233,7 @@ function LGAudioSource() this.loadSound( this.properties.src ); } +LGAudioSource["@src"] = { widget: "resource" }; LGAudioSource.supported_extensions = ["wav","ogg","mp3"]; @@ -12586,11 +13245,11 @@ LGAudioSource.prototype.onAdded = function(graph) LGAudioSource.prototype.onStart = function() { - if(!this._audio_buffer) + if(!this._audiobuffer) return; if(this.properties.autoplay) - this.playBuffer( this._audio_buffer ); + this.playBuffer( this._audiobuffer ); } LGAudioSource.prototype.onStop = function() @@ -12598,6 +13257,18 @@ 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(); @@ -12610,38 +13281,62 @@ LGAudioSource.prototype.stopAllSounds = function() //iterate and stop for(var i = 0; i < this._audionodes.length; ++i ) { - this._audionodes[i].stop(); + 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) - return; + 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; + } + } - for(var i = 0; i < this.inputs.length; ++i) - { - var input = this.inputs[i]; - if(!input.link) - 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; - } + 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._audio_buffer) + if(this._audiobuffer) { if(event == "Play") - this.playBuffer(this._audio_buffer); + this.playBuffer(this._audiobuffer); else if(event == "Stop") this.stopAllSounds(); } @@ -12653,6 +13348,11 @@ LGAudioSource.prototype.onPropertyChanged = function( name, value ) 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 ) @@ -12662,6 +13362,7 @@ LGAudioSource.prototype.playBuffer = function( buffer ) //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; @@ -12680,7 +13381,11 @@ LGAudioSource.prototype.playBuffer = function( buffer ) that._audionodes.splice(index,1); } - audionode.start(); + if(!audionode.started) + { + audionode.started = true; + audionode.start(); + } return audionode; } @@ -12695,7 +13400,7 @@ LGAudioSource.prototype.loadSound = function( url ) this._request = null; } - this._audio_buffer = null; + this._audiobuffer = null; //points to the audiobuffer once the audio is loaded this._loading_audio = false; if(!url) @@ -12709,7 +13414,7 @@ LGAudioSource.prototype.loadSound = function( url ) function inner( buffer ) { this.boxcolor = LiteGraph.NODE_DEFAULT_BOXCOLOR; - that._audio_buffer = buffer; + that._audiobuffer = buffer; that._loading_audio = false; //if is playing, then play it if(that.graph && that.graph.status === LGraph.STATUS_RUNNING) @@ -12727,7 +13432,7 @@ LGAudioSource.prototype.onGetInputs = function() LGAudioSource.prototype.onGetOutputs = function() { - return [["ended",LiteGraph.EVENT]]; + return [["buffer","audiobuffer"],["ended",LiteGraph.EVENT]]; } LGAudioSource.prototype.onDropFile = function(file) @@ -12768,7 +13473,7 @@ function LGAudioAnalyser() this.addInput("in","audio"); this.addOutput("freqs","array"); - //this.addOutput("time","freq"); + this.addOutput("samples","array"); this._freq_bin = null; this._time_bin = null; @@ -12793,14 +13498,21 @@ LGAudioAnalyser.prototype.onExecute = function() //send analyzer if(this.isOutputConnected(1)) - this.setOutputData(1,this.audionode); + { + //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) + if(input.link == null) continue; var v = this.getInputData(i); if (v !== undefined) @@ -12818,12 +13530,11 @@ LGAudioAnalyser.prototype.onGetInputs = function() return [["minDecibels","number"],["maxDecibels","number"],["smoothingTimeConstant","number"]]; } -/* LGAudioAnalyser.prototype.onGetOutputs = function() { - return [["Analyzer","analyzer"]]; + return [["freqs","array"],["samples","array"]]; } -*/ + LGAudioAnalyser.title = "Analyser"; LGAudioAnalyser.desc = "Audio Analyser"; @@ -12964,7 +13675,7 @@ LGAudioDynamicsCompressor.prototype.onExecute = function() for(var i = 1; i < this.inputs.length; ++i) { var input = this.inputs[i]; - if(!input.link) + if(input.link == null) continue; var v = this.getInputData(i); if(v !== undefined) @@ -13051,6 +13762,15 @@ LGAudioMixer.prototype.getAudioNodeInInputSlot = function( slot ) 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) @@ -13060,7 +13780,7 @@ LGAudioMixer.prototype.onExecute = function() { var input = this.inputs[i]; - if(!input.link || input.type == "audio") + if(input.link == null || input.type == "audio") continue; var v = this.getInputData(i); @@ -13135,7 +13855,7 @@ LGAudioBiquadFilter.prototype.onExecute = function() for(var i = 1; i < this.inputs.length; ++i) { var input = this.inputs[i]; - if(!input.link) + if(input.link == null) continue; var v = this.getInputData(i); if(v !== undefined) @@ -13155,6 +13875,81 @@ 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 @@ -13167,7 +13962,7 @@ function LGAudioVisualization() mark: -1 }; - this.addInput("freqs","array"); + this.addInput("data","array"); this.addInput("mark","number"); this.size = [300,200]; this._last_buffer = null; @@ -13292,6 +14087,137 @@ 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() { diff --git a/build/litegraph.min.js b/build/litegraph.min.js index 42ced6e8b..c80ef712d 100755 --- a/build/litegraph.min.js +++ b/build/litegraph.min.js @@ -1,375 +1,400 @@ -(function(d){function e(a,b){return Math.sqrt((b[0]-a[0])*(b[0]-a[0])+(b[1]-a[1])*(b[1]-a[1]))}function f(a,b,c,g,l,e){return ca&&gb?!0:!1}function k(a,b){return a[0]>b[2]||a[1]>b[3]||a[2]=h.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.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={}};LGraphNode.prototype.configure=function(a){for(var b in a)if("console"!=b)if("properties"==b)for(var c in a.properties){if(this.properties[c]=a.properties[c],this.onPropertyChanged)this.onPropertyChanged(c,a.properties[c])}else null!= -a[b]&&("object"==typeof a[b]?this[b]&&this[b].configure?this[b].configure(a[b]):this[b]=h.cloneObject(a[b],this[b]):this[b]=a[b]);if(this.onConnectionsChange){if(this.inputs)for(var g=0;g=this.inputs.length||null==this.inputs[a].link)){var c=this.graph.links[this.inputs[a].link];if(!b)return c.data;var g=this.graph.getNodeById(c.origin_id);if(!g)return c.data;if(g.updateOutputData)g.updateOutputData(c.origin_slot);else if(g.onExecute)g.onExecute();return c.data}};LGraphNode.prototype.isInputConnected=function(a){return this.inputs?a=this.inputs.length)return null;a=this.inputs[a];return a&&a.link?(a=this.graph.links[a.link])?this.graph.getNodeById(a.origin_id):null:null};LGraphNode.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=[],c=0;ca&&this.pos[1]-g-cb)return!0;return!1};LGraphNode.prototype.getSlotInPosition=function(a,b){if(this.inputs)for(var c=0,g=this.inputs.length;c=this.outputs.length)return h.debug&&console.log("Connect: Error, slot number not found"),!1;b&&b.constructor===Number&&(b=this.graph.getNodeById(b));if(!b)throw"Node not found";if(b==this)return!1;if(c.constructor===String){if(c=b.findInputSlot(c),-1==c)return h.debug&&console.log("Connect: Error, no slot of name "+c),!1}else{if(c===h.EVENT)return!1;if(!b.inputs|| -c>=b.inputs.length)return h.debug&&console.log("Connect: Error, slot number not found"),!1}null!=b.inputs[c].link&&b.disconnectInput(c);this.setDirtyCanvas(!1,!0);this.graph.connectionChange(this);var g=this.outputs[a];if(b.onConnectInput&&!1===b.onConnectInput(c,g.type,g))return!1;if(h.isValidConnection(g.type,b.inputs[c].type)){var l={id:this.graph.last_link_id++,origin_id:this.id,origin_slot:a,target_id:b.id,target_slot:c};this.graph.links[l.id]=l;null==g.links&&(g.links=[]);g.links.push(l.id); -b.inputs[c].link=l.id;if(this.onConnectionsChange)this.onConnectionsChange(h.OUTPUT,a,!0,l);if(b.onConnectionsChange)b.onConnectionsChange(h.INPUT,c,!0,l)}this.setDirtyCanvas(!1,!0);this.graph.connectionChange(this);return!0};LGraphNode.prototype.disconnectOutput=function(a,b){if(a.constructor===String){if(a=this.findOutputSlot(a),-1==a)return h.debug&&console.log("Connect: Error, no slot of name "+a),!1}else if(!this.outputs||a>=this.outputs.length)return h.debug&&console.log("Connect: Error, slot number not found"), -!1;var c=this.outputs[a];if(!c.links||0==c.links.length)return!1;if(b){b.constructor===Number&&(b=this.graph.getNodeById(b));if(!b)throw"Target Node not found";for(var g=0,l=c.links.length;g=this.inputs.length)return h.debug&&console.log("Connect: Error, slot number not found"),!1;if(!this.inputs[a])return!1;var b=this.inputs[a].link;this.inputs[a].link=null;if(b=this.graph.links[b]){var c=this.graph.getNodeById(b.origin_id);if(!c)return!1;var g=c.outputs[b.origin_slot];if(!g||!g.links||0==g.links.length)return!1;for(var l=0,d=g.links.length;l -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*h.NODE_SLOT_HEIGHT]:[this.pos[0]+this.size[0]+1,this.pos[1]+10+b*h.NODE_SLOT_HEIGHT]};LGraphNode.prototype.alignToGrid=function(){this.pos[0]=h.CANVAS_GRID_SIZE*Math.round(this.pos[0]/h.CANVAS_GRID_SIZE);this.pos[1]=h.CANVAS_GRID_SIZE*Math.round(this.pos[1]/ -h.CANVAS_GRID_SIZE)};LGraphNode.prototype.trace=function(a){this.console||(this.console=[]);this.console.push(a);this.console.length>LGraphNode.MAX_CONSOLE&&this.console.shift();this.graph.onNodeTrace(this,a)};LGraphNode.prototype.setDirtyCanvas=function(a,b){this.graph&&this.graph.sendActionToCanvas("setDirty",[a,b])};LGraphNode.prototype.loadImage=function(a){var b=new Image;b.src=h.node_images_path+a;b.ready=!1;var c=this;b.onload=function(){this.ready=!0;c.setDirtyCanvas(!0)};return b};LGraphNode.prototype.captureInput= -function(a){if(this.graph&&this.graph.list_of_graphcanvas)for(var b=this.graph.list_of_graphcanvas,c=0;ch.getTime()-this.last_mouseclick&&this.selected_nodes[c.id]){if(c.onDblClick)c.onDblClick(a);this.processNodeDblClicked(c); -l=!0}c.onMouseDown&&c.onMouseDown(a,[a.canvasX-c.pos[0],a.canvasY-c.pos[1]])?l=!0:this.live_mode&&(l=g=!0);l||(this.allow_dragnodes&&(this.node_dragged=c),this.selected_nodes[c.id]||this.processNodeSelected(c,a));this.dirty_canvas=!0}}else g=!0;g&&this.allow_dragcanvas&&(this.dragging_canvas=!0)}else 2!=a.which&&3==a.which&&this.processContextMenu(c,a);this.last_mouse[0]=a.localX;this.last_mouse[1]=a.localY;this.last_mouseclick=h.getTime();this.canvas_mouse=[a.canvasX,a.canvasY];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}};LGraphCanvas.prototype.processMouseMove=function(a){this.autoresize&&this.resize();if(this.graph){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=[a.canvasX,a.canvasY];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);for(var b=this.graph.getNodeOnPos(a.canvasX,a.canvasY,this.visible_nodes),g=0,l=this.graph._nodes.length;gb&&(c*=1/1.1);this.setZoom(c,[a.localX,a.localY]);this.graph.change();a.preventDefault();return!1}};LGraphCanvas.prototype.isOverNodeBox=function(a,b,c){var g=h.NODE_TITLE_HEIGHT;return f(b,c,a.pos[0]+2,a.pos[1]+2-g,g-4,g-4)?!0:!1};LGraphCanvas.prototype.isOverNodeInput=function(a,b,c,g){if(a.inputs)for(var l=0,d=a.inputs.length;lthis.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}};LGraphCanvas.prototype.drawFrontCanvas=function(){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]);this.visible_nodes=b=this.computeVisibleNodes();for(var c=0;cb-f._last_time&&(f=2-0.002*(b-f._last_time), -k="rgba(255,255,255, "+f.toFixed(2)+")",this.renderLink(a,d,l.getConnectionPos(!0,e),k,!0,f))}}}}}a.globalAlpha=1};LGraphCanvas.prototype.renderLink=function(a,b,c,g,l,d){if(this.highquality_render){var f=e(b,c);this.render_connections_border&&0.6this.scale)&&(this.render_connection_arrows&&0.6b[1]?0:Math.PI,a.save(), -a.translate(g[0],g[1]),a.rotate(f),a.beginPath(),a.moveTo(-5,-5),a.lineTo(0,5),a.lineTo(5,-5),a.fill(),a.restore()),d))for(d=0;5>d;++d)g=(0.001*h.getTime()+0.2*d)%1,g=this.computeConnectionPoint(b,c,g),a.beginPath(),a.arc(g[0],g[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()};LGraphCanvas.prototype.computeConnectionPoint=function(a,b,c){var g=e(a,b),l=[a[0]+0.25*g,a[1]],g=[b[0]-0.25*g,b[1]],d=(1-c)*(1-c)*(1-c),f=3*(1-c)*(1-c)*c,h=3*(1-c)*c*c;c*=c* -c;return[d*a[0]+f*l[0]+h*g[0]+c*b[0],d*a[1]+f*l[1]+h*g[1]+c*b[1]]};LGraphCanvas.prototype.resize=function(a,b){if(!a&&!b){var c=this.canvas.parentNode;a=c.offsetWidth;b=c.offsetHeight}if(this.canvas.width!=a||this.canvas.height!=b)this.canvas.width=a,this.canvas.height=b,this.bgcanvas.width=this.canvas.width,this.bgcanvas.height=this.canvas.height,this.setDirty(!0,!0)};LGraphCanvas.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 g=setInterval(function(){b.editor_alpha*=c;b.dirty_canvas=!0;b.dirty_bgcanvas=!0;1>c&&0.01>b.editor_alpha&&(clearInterval(g),1>c&&(b.live_mode=!0));1"+k+""+ -n+"",value:k})}if(f.length)return h.createContextMenu(f,{event:b,callback:g,from:c,allow_html:!0},d),!1}};LGraphCanvas.decodeHTML=function(a){var b=document.createElement("div");b.innerText=a;return b.innerHTML};LGraphCanvas.onShowTitleEditor=function(a,b){function c(){a.title=e.value;g.parentNode.removeChild(g);a.setDirtyCanvas(!0,!0)}var g=document.createElement("div");g.className="graphdialog";g.innerHTML="Title"; -var e=g.querySelector("input");e&&(e.value=a.title,e.addEventListener("keydown",function(a){13==a.keyCode&&(c(),a.preventDefault(),a.stopPropagation())}));var d=this.canvas.getClientRects()[0],f=-20,h=-20;d&&(f-=d.left,h-=d.top);b?(g.style.left=b.pageX+f+"px",g.style.top=b.pageY+h+"px"):(g.style.left=0.5*this.canvas.width+f+"px",g.style.top=0.5*this.canvas.height+h+"px");g.querySelector("button").addEventListener("click",c);this.canvas.parentNode.appendChild(g)};LGraphCanvas.prototype.showEditPropertyValue= -function(a,b,c){function g(){e(s.value)}function e(c){"number"==typeof a.properties[b]&&(c=Number(c));a.properties[b]=c;if(a.onPropertyChanged)a.onPropertyChanged(b,c);p.parentNode.removeChild(p);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]);var f=null;a.getPropertyInfo&&(f=a.getPropertyInfo(b));if(a.properties_info)for(var h=0;h";else if("enum"==d&&f.values){k=""}else"boolean"==d&&(k="");var p= -document.createElement("div");p.className="graphdialog";p.innerHTML=""+b+""+k+"";if("enum"==d&&f.values){var s=p.querySelector("select");s.addEventListener("change",function(a){e(a.target.value)})}else if("boolean"==d)(s=p.querySelector("input"))&&s.addEventListener("click",function(a){e(!!s.checked)});else if(s=p.querySelector("input"))s.value=void 0!==a.properties[b]?a.properties[b]:"",s.addEventListener("keydown",function(a){13==a.keyCode&&(g(),a.preventDefault(), -a.stopPropagation())});d=this.canvas.getClientRects()[0];h=f=-20;d&&(f-=d.left,h-=d.top);c.event?(p.style.left=c.event.pageX+f+"px",p.style.top=c.event.pageY+h+"px"):(p.style.left=0.5*this.canvas.width+f+"px",p.style.top=0.5*this.canvas.height+h+"px");p.querySelector("button").addEventListener("click",g);this.canvas.parentNode.appendChild(p)}};LGraphCanvas.onMenuNodeCollapse=function(a){a.flags.collapsed=!a.flags.collapsed;a.setDirtyCanvas(!0,!0)};LGraphCanvas.onMenuNodePin=function(a){a.pin()};LGraphCanvas.onMenuNodeMode= -function(a,b,c){h.createContextMenu(["Always","On Event","Never"],{event:b,callback:function(b){if(a)switch(b){case "On Event":a.mode=h.ON_EVENT;break;case "Never":a.mode=h.NEVER;break;default:a.mode=h.ALWAYS}},from:c});return!1};LGraphCanvas.onMenuNodeColors=function(a,b,c){var g=[],d;for(d in LGraphCanvas.node_colors){var e=LGraphCanvas.node_colors[d];g.push({value:d,content:""+d+""})}h.createContextMenu(g,{event:b, -callback:function(b){a&&(b=LGraphCanvas.node_colors[b.value])&&(a.color=b.color,a.bgcolor=b.bgcolor,a.setDirtyCanvas(!0))},from:c});return!1};LGraphCanvas.onMenuNodeShapes=function(a,b){h.createContextMenu(["box","round"],{event:b,callback:function(b){a&&(a.shape=b,a.setDirtyCanvas(!0))}});return!1};LGraphCanvas.onMenuNodeRemove=function(a){!1!=a.removable&&(a.graph.remove(a),a.setDirtyCanvas(!0,!0))};LGraphCanvas.onMenuNodeClone=function(a){if(!1!=a.clonable){var b=a.clone();b&&(b.pos=[a.pos[0]+ -5,a.pos[1]+5],a.graph.add(b),a.setDirtyCanvas(!0,!0))}};LGraphCanvas.node_colors={red:{color:"#FAA",bgcolor:"#A44"},green:{color:"#AFA",bgcolor:"#4A4"},blue:{color:"#AAF",bgcolor:"#44A"},white:{color:"#FFF",bgcolor:"#AAA"}};LGraphCanvas.prototype.getCanvasMenuOptions=function(){var a=null;this.getMenuOptions?a=this.getMenuOptions():(a=[{content:"Add Node",is_menu:!0,callback:LGraphCanvas.onMenuAdd}],this._graph_stack&&0a[2]&&(a[2]=b);ca[3]&&(a[3]=c)};h.isInsideBounding=function(a,b){return a[0]b[1][0]||a[1]>b[1][1]?!1:!0};h.overlapBounding=k;h.hex2num=function(a){"#"==a.charAt(0)&&(a=a.slice(1));a=a.toUpperCase();for(var b=Array(3),c=0,g,d,e=0;6>e;e+=2)g="0123456789ABCDEF".indexOf(a.charAt(e)),d="0123456789ABCDEF".indexOf(a.charAt(e+1)),b[c]=16*g+d,c++;return b};h.num2hex=function(a){for(var b="#",c,g,d=0;3>d;d++)c=a[d]/16,g=a[d]%16,b+= -"0123456789ABCDEF".charAt(c)+"0123456789ABCDEF".charAt(g);return b};h.createContextMenu=function(a,b,c){function g(a){var g=!0;b.callback&&(a=b.callback.call(f,this.data,a),void 0!==a&&(g=a));g&&h.closeAllContextMenus(c)}this.options=b=b||{};c=c||window;if(b.from){var d=document.querySelectorAll(".graphcontextmenu"),e;for(e in d)d[e].previousSibling==b.from&&d[e].closeMenu()}else h.closeAllContextMenus(c);var f=c.document.createElement("div");f.className="graphcontextmenu graphmenubar-panel";this.root= -f;d=f.style;d.minWidth="100px";d.minHeight="20px";d.position="fixed";d.top="100px";d.left="100px";d.color="#AAF";d.padding="2px";d.borderBottom="2px solid #AAF";d.backgroundColor="#444";d.zIndex=10;b.title&&(d=document.createElement("div"),d.className="graphcontextmenu-title",d.innerHTML=b.title,f.appendChild(d));f.addEventListener("contextmenu",function(a){a.preventDefault();return!1});for(var k in a){e=a[k];d=c.document.createElement("div");d.className="graphmenu-entry";if(null==e)d.className+= -" separator";else{e.is_menu&&(d.className+=" submenu");e.disabled&&(d.className+=" disabled");e.className&&(d.className+=" "+e.className);d.style.cursor="pointer";d.dataset.value="string"==typeof e?e:e.value;d.data=e;var n="",n="string"==typeof e?a.constructor==Array?a[k]:k:e.content?e.content:k;b.allow_html?d.innerHTML=n:d.innerText=n;d.addEventListener("click",g)}f.appendChild(d)}f.addEventListener("mouseover",function(a){this.mouse_inside=!0});f.addEventListener("mouseout",function(a){for(a=a.relatedTarget|| -a.toElement;a!=this&&a!=c.document;)a=a.parentNode;a!=this&&(this.mouse_inside=!1,this.block_close||this.closeMenu())});c.document.body.appendChild(f);a=f.getClientRects()[0];b.from&&(b.from.block_close=!0);e=b.left||0;k=b.top||0;b.event&&(e=b.event.pageX-10,k=b.event.pageY-10,b.left&&(e=b.left),d=c.document.body.getClientRects()[0],b.from&&(e=b.from.getClientRects()[0],e=e.left+e.width),e>d.width-a.width-10&&(e=d.width-a.width-10),k>d.height-a.height-10&&(k=d.height-a.height-10));f.style.left=e+ -"px";f.style.top=k+"px";f.closeMenu=function(){b.from&&(b.from.block_close=!1,b.from.mouse_inside||b.from.closeMenu());this.parentNode&&c.document.body.removeChild(this)};return f};h.closeAllContextMenus=function(a){a=a||window;a=a.document.querySelectorAll(".graphcontextmenu");if(a.length){for(var b=[],c=0;ca&&gb?!0:!1}function h(a,b){return a[0]>b[2]||a[1]>b[3]||a[2]e.width-f.width-10&&(k=e.width-f.width-10);d>e.height-f.height-10&&(d=e.height-f.height-10)}g.style.left=k+"px";g.style.top=d+"px"}var l=e.LiteGraph={NODE_TITLE_HEIGHT:16, +NODE_SLOT_HEIGHT:15,NODE_WIDTH:140,NODE_MIN_WIDTH:50,NODE_COLLAPSED_RADIUS:10,NODE_COLLAPSED_WIDTH:80,CANVAS_GRID_SIZE:10,NODE_TITLE_COLOR:"#222",NODE_DEFAULT_COLOR:"#999",NODE_DEFAULT_BGCOLOR:"#444",NODE_DEFAULT_BOXCOLOR:"#AEF",NODE_DEFAULT_SHAPE:"box",MAX_NUMBER_OF_NODES:1E3,DEFAULT_POSITION:[100,100],node_images_path:"",INPUT:1,OUTPUT:2,EVENT:-1,ACTION:-1,ALWAYS:0,ON_EVENT:1,NEVER:2,proxy:null,debug:!1,throw_errors:!0,allow_scripts:!0,registered_node_types:{},node_types_by_file_extension:{},Nodes:{}, +registerNodeType:function(a,b){if(!b.prototype)throw"Cannot register a simple object, it must be a class with a prototype";b.type=a;l.debug&&console.log("Node registered: "+a);a.split("/");var c=b.constructor.name,g=a.lastIndexOf("/");b.category=a.substr(0,g);b.title||(b.title=c);if(b.prototype)for(var k in LGraphNode.prototype)b.prototype[k]||(b.prototype[k]=LGraphNode.prototype[k]);this.registered_node_types[a]=b;b.constructor.name&&(this.Nodes[c]=b);b.prototype.onPropertyChange&&console.warn("LiteGraph node class "+ +a+" has onPropertyChange method, it must be called onPropertyChanged with d at the end");if(b.supported_extensions)for(k in b.supported_extensions)this.node_types_by_file_extension[b.supported_extensions[k].toLowerCase()]=b},addNodeMethod:function(a,b){LGraphNode.prototype[a]=b;for(var c in this.registered_node_types){var g=this.registered_node_types[c];g.prototype[a]&&(g.prototype["_"+a]=g.prototype[a]);g.prototype[a]=b}},createNode:function(a,b,c){var g=this.registered_node_types[a];if(!g)return l.debug&& +console.log('GraphNode type "'+a+'" not registered.'),null;b=b||g.title||a;g=new g(name);g.type=a;g.title||(g.title=b);g.properties||(g.properties={});g.properties_info||(g.properties_info=[]);g.flags||(g.flags={});g.size||(g.size=g.computeSize());g.pos||(g.pos=l.DEFAULT_POSITION.concat());g.mode||(g.mode=l.ALWAYS);if(c)for(var k in c)g[k]=c[k];return g},getNodeType:function(a){return this.registered_node_types[a]},getNodeTypesInCategory:function(a){var b=[],c;for(c in this.registered_node_types)""== +a?null==this.registered_node_types[c].category&&b.push(this.registered_node_types[c]):this.registered_node_types[c].category==a&&b.push(this.registered_node_types[c]);return b},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 c=[];for(b in a)c.push(b);return c},reloadNodes:function(a){var b=document.getElementsByTagName("script"),c= +[],g;for(g in b)c.push(b[g]);b=document.getElementsByTagName("head")[0];a=document.location.href+a;for(g in c){var k=c[g].src;if(k&&k.substr(0,a.length)==a)try{l.debug&&console.log("Reloading: "+k);var d=document.createElement("script");d.type="text/javascript";d.src=k;b.appendChild(d);b.removeChild(c[g])}catch(e){if(l.throw_errors)throw e;l.debug&&console.log("Error while reloading "+k)}}l.debug&&console.log("Nodes reloaded")},cloneObject:function(a,b){if(null==a)return null;var c=JSON.parse(JSON.stringify(a)); +if(!b)return c;for(var g in c)b[g]=c[g];return b},isValidConnection:function(a,b){return!a||!b||a==a||a!==l.EVENT&&b!==l.EVENT&&a.toLowerCase()==b.toLowerCase()?!0:!1}};l.getTime="undefined"!=typeof performance?function(){return performance.now()}:function(){return Date.now()};e.LGraph=l.LGraph=function(){l.debug&&console.log("Graph created");this.list_of_graphcanvas=null;this.clear()};LGraph.supported_types=["number","string","boolean"];LGraph.prototype.getSupportedTypes=function(){return this.supported_types|| +LGraph.supported_types};LGraph.STATUS_STOPPED=1;LGraph.STATUS_RUNNING=2;LGraph.prototype.clear=function(){this.stop();this.status=LGraph.STATUS_STOPPED;this.last_node_id=0;this._nodes=[];this._nodes_by_id={};this._nodes_executable=this._nodes_in_order=null;this.last_link_id=0;this.links={};this.iteration=0;this.config={};this.fixedtime=this.runningtime=this.globaltime=0;this.elapsed_time=this.fixedtime_lapse=0.01;this.starttime=0;this.catch_errors=!0;this.global_inputs={};this.global_outputs={};this.debug= +!0;this.change();this.sendActionToCanvas("clear")};LGraph.prototype.attachCanvas=function(a){if(a.constructor!=LGraphCanvas)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)};LGraph.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))}}; +LGraph.prototype.start=function(a){if(this.status!=LGraph.STATUS_RUNNING){this.status=LGraph.STATUS_RUNNING;if(this.onPlayEvent)this.onPlayEvent();this.sendEventToAllNodes("onStart");this.starttime=l.getTime();var b=this;this.execution_timer_id=setInterval(function(){b.runStep(1,!this.catch_errors)},a||1)}};LGraph.prototype.stop=function(){if(this.status!=LGraph.STATUS_STOPPED){this.status=LGraph.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")}};LGraph.prototype.runStep=function(a,b){a=a||1;var c=l.getTime();this.globaltime=0.001*(c-this.starttime);var g=this._nodes_executable?this._nodes_executable:this._nodes;if(g){if(b){for(var k=0;k=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.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={}};LGraphNode.prototype.configure=function(a){for(var b in a)if("console"!=b)if("properties"==b)for(var c in a.properties){if(this.properties[c]=a.properties[c],this.onPropertyChanged)this.onPropertyChanged(c,a.properties[c])}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]);if(this.onConnectionsChange){if(this.inputs)for(var g=0;g=this.outputs.length)){var c=this.outputs[a];if(c&&(c._data=b,this.outputs[a].links))for(c=0;c=this.inputs.length||null==this.inputs[a].link)){var c=this.graph.links[this.inputs[a].link];if(!b)return c.data;var g=this.graph.getNodeById(c.origin_id);if(!g)return c.data;if(g.updateOutputData)g.updateOutputData(c.origin_slot);else if(g.onExecute)g.onExecute();return c.data}};LGraphNode.prototype.isInputConnected=function(a){return this.inputs?a=this.inputs.length)return null;a=this.inputs[a];return a&&a.link?(a=this.graph.links[a.link])?this.graph.getNodeById(a.origin_id):null:null};LGraphNode.prototype.getOutputData=function(a){return!this.outputs||a>=this.outputs.length?null:this.outputs[a]._data};LGraphNode.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=[],c=0;ca&&this.pos[1]-g-cb)return!0;return!1};LGraphNode.prototype.getSlotInPosition=function(a,b){if(this.inputs)for(var c=0,g=this.inputs.length;c=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"Node not found";if(b==this)return!1;if(c.constructor=== +String){if(c=b.findInputSlot(c),-1==c)return l.debug&&console.log("Connect: Error, no slot of name "+c),!1}else{if(c===l.EVENT)return!1;if(!b.inputs||c>=b.inputs.length)return l.debug&&console.log("Connect: Error, slot number not found"),!1}null!=b.inputs[c].link&&b.disconnectInput(c);this.setDirtyCanvas(!1,!0);this.graph.connectionChange(this);var g=this.outputs[a];if(b.onConnectInput&&!1===b.onConnectInput(c,g.type,g))return!1;var k=b.inputs[c];if(l.isValidConnection(g.type,k.type)){var d={id:this.graph.last_link_id++, +type:k.type,origin_id:this.id,origin_slot:a,target_id:b.id,target_slot:c};this.graph.links[d.id]=d;null==g.links&&(g.links=[]);g.links.push(d.id);b.inputs[c].link=d.id;if(this.onConnectionsChange)this.onConnectionsChange(l.OUTPUT,a,!0,d,g);if(b.onConnectionsChange)b.onConnectionsChange(l.INPUT,c,!0,d,k)}this.setDirtyCanvas(!1,!0);this.graph.connectionChange(this);return!0};LGraphNode.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 c=this.outputs[a];if(!c.links||0==c.links.length)return!1;if(b){b.constructor===Number&&(b=this.graph.getNodeById(b));if(!b)throw"Target Node not found";for(var g=0,k=c.links.length;g=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 c=this.inputs[a].link;this.inputs[a].link=null;if(c=this.graph.links[c]){var g=this.graph.getNodeById(c.origin_id); +if(!g)return!1;var k=g.outputs[c.origin_slot];if(!k||!k.links||0==k.links.length)return!1;for(var d=0,e=k.links.length;db&&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.pos[0]+this.size[0]+1,this.pos[1]+10+b*l.NODE_SLOT_HEIGHT]}; +LGraphNode.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)};LGraphNode.prototype.trace=function(a){this.console||(this.console=[]);this.console.push(a);this.console.length>LGraphNode.MAX_CONSOLE&&this.console.shift();this.graph.onNodeTrace(this,a)};LGraphNode.prototype.setDirtyCanvas=function(a,b){this.graph&&this.graph.sendActionToCanvas("setDirty",[a,b])};LGraphNode.prototype.loadImage= +function(a){var b=new Image;b.src=l.node_images_path+a;b.ready=!1;var c=this;b.onload=function(){this.ready=!0;c.setDirtyCanvas(!0)};return b};LGraphNode.prototype.captureInput=function(a){if(this.graph&&this.graph.list_of_graphcanvas)for(var b=this.graph.list_of_graphcanvas,c=0;c element, you passed a "+a.localName;throw"This browser doesnt support Canvas";}null==(this.ctx=a.getContext("2d"))&&(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()}};LGraphCanvas.prototype._doNothing=function(a){a.preventDefault();return!1};LGraphCanvas.prototype._doReturnTrue= +function(a){a.preventDefault();return!0};LGraphCanvas.prototype.bindEvents=function(){if(this._events_binded)console.warn("LGraphCanvas: events already binded");else{var a=this.canvas;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);a.addEventListener("keyup",this._key_callback);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}};LGraphCanvas.prototype.unbindEvents=function(){this._events_binded?(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),this.canvas.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):console.warn("LGraphCanvas: no events binded")};LGraphCanvas.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()};LGraphCanvas.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};LGraphCanvas.prototype.setDirty=function(a,b){a&&(this.dirty_canvas=!0);b&&(this.dirty_bgcanvas=!0)};LGraphCanvas.prototype.getCanvasWindow=function(){if(!this.canvas)return window;var a=this.canvas.ownerDocument;return a.defaultView||a.parentWindow};LGraphCanvas.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))};LGraphCanvas.prototype.stopRendering=function(){this.is_rendering=!1};LGraphCanvas.prototype.processMouseDown=function(a){if(this.graph){this.adjustMouseEvent(a);var b=this.getCanvasWindow();LGraphCanvas.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 c=this.graph.getNodeOnPos(a.canvasX,a.canvasY,this.visible_nodes);l.closeAllContextMenus(b);if(1==a.which){if(!(a.shiftKey||c&&this.selected_nodes[c.id])){var g=[],k;for(k in this.selected_nodes)this.selected_nodes[k]!=c&&g.push(this.selected_nodes[k]);for(k in g)this.processNodeDeselected(g[k])}g=!1;if(c&&this.allow_interaction){this.live_mode||c.flags.pinned||this.bringToFront(c);var d=!1;if(!this.connecting_node&&!c.flags.collapsed&&!this.live_mode){if(c.outputs){k=0;for(var e=c.outputs.length;k< +e;++k){var h=c.outputs[k],n=c.getConnectionPos(!1,k);if(f(a.canvasX,a.canvasY,n[0]-10,n[1]-5,20,10)){this.connecting_node=c;this.connecting_output=h;this.connecting_pos=c.getConnectionPos(!1,k);this.connecting_slot=k;d=!0;break}}}if(c.inputs)for(k=0,e=c.inputs.length;kl.getTime()-this.last_mouseclick&&this.selected_nodes[c.id]){if(c.onDblClick)c.onDblClick(a);this.processNodeDblClicked(c);k=!0}c.onMouseDown&&c.onMouseDown(a,[a.canvasX-c.pos[0],a.canvasY-c.pos[1]])?k=!0:this.live_mode&&(k=g=!0);k||(this.allow_dragnodes&&(this.node_dragged=c),this.selected_nodes[c.id]||this.processNodeSelected(c, +a));this.dirty_canvas=!0}}else g=!0;g&&this.allow_dragcanvas&&(this.dragging_canvas=!0)}else 2!=a.which&&3==a.which&&this.processContextMenu(c,a);this.last_mouse[0]=a.localX;this.last_mouse[1]=a.localY;this.last_mouseclick=l.getTime();this.canvas_mouse=[a.canvasX,a.canvasY];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}};LGraphCanvas.prototype.processMouseMove=function(a){this.autoresize&&this.resize();if(this.graph){LGraphCanvas.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=[a.canvasX,a.canvasY];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);for(var b=this.graph.getNodeOnPos(a.canvasX,a.canvasY,this.visible_nodes),g=0,k=this.graph._nodes.length;gb&&(c*=1/1.1);this.setZoom(c,[a.localX,a.localY]);this.graph.change();a.preventDefault();return!1}};LGraphCanvas.prototype.isOverNodeBox= +function(a,b,c){var g=l.NODE_TITLE_HEIGHT;return f(b,c,a.pos[0]+2,a.pos[1]+2-g,g-4,g-4)?!0:!1};LGraphCanvas.prototype.isOverNodeInput=function(a,b,c,g){if(a.inputs)for(var k=0,d=a.inputs.length;kthis.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}};LGraphCanvas.prototype.drawFrontCanvas=function(){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]);this.visible_nodes=b=this.computeVisibleNodes();for(var c=0;cb-e._last_time&&(f=2-0.002*(b-e._last_time),h="rgba(255,255,255, "+f.toFixed(2)+")",this.renderLink(a,n,k.getConnectionPos(!0,d),e,!0,f,h))}}}}a.globalAlpha=1};LGraphCanvas.prototype.renderLink=function(a,b,c,g,k,e,f){if(this.highquality_render){var h=d(b,c);this.render_connections_border&&0.6b[1]?0:Math.PI,a.save(),a.translate(g[0],g[1]),a.rotate(f),a.beginPath(),a.moveTo(-5,-5),a.lineTo(0,5),a.lineTo(5,-5),a.fill(),a.restore());if(e)for(e=0;5>e;++e)g=(0.001*l.getTime()+0.2*e)%1,g=this.computeConnectionPoint(b,c,g),a.beginPath(),a.arc(g[0], +g[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()};LGraphCanvas.prototype.computeConnectionPoint=function(a,b,c){var g=d(a,b),k=[a[0]+0.25*g,a[1]],g=[b[0]-0.25*g,b[1]],e=(1-c)*(1-c)*(1-c),f=3*(1-c)*(1-c)*c,h=3*(1-c)*c*c;c*=c*c;return[e*a[0]+f*k[0]+h*g[0]+c*b[0],e*a[1]+f*k[1]+h*g[1]+c*b[1]]};LGraphCanvas.prototype.resize=function(a,b){if(!a&&!b){var c=this.canvas.parentNode;a=c.offsetWidth;b=c.offsetHeight}if(this.canvas.width!=a||this.canvas.height!= +b)this.canvas.width=a,this.canvas.height=b,this.bgcanvas.width=this.canvas.width,this.bgcanvas.height=this.canvas.height,this.setDirty(!0,!0)};LGraphCanvas.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 g=setInterval(function(){b.editor_alpha*=c;b.dirty_canvas=!0;b.dirty_bgcanvas=!0;1>c&&0.01>b.editor_alpha&&(clearInterval(g),1>c&&(b.live_mode=!0));1"+h+""+ +a+"",value:h});if(f.length)return new l.ContextMenu(f,{event:c,callback:d,parentMenu:g,allow_html:!0,node:k},b),!1}};LGraphCanvas.decodeHTML=function(a){var b=document.createElement("div");b.innerText=a;return b.innerHTML};LGraphCanvas.onResizeNode=function(a,b,c,g,d){d&&(d.size=d.computeSize(),d.setDirtyCanvas(!0,!0))};LGraphCanvas.onShowTitleEditor=function(a,b,c,g,d){function e(){d.title=h.value;f.parentNode.removeChild(f);d.setDirtyCanvas(!0,!0)}var f=document.createElement("div");f.className= +"graphdialog";f.innerHTML="Title";var h=f.querySelector("input");h&&(h.value=d.title,h.addEventListener("keydown",function(a){13==a.keyCode&&(e(),a.preventDefault(),a.stopPropagation())}));a=LGraphCanvas.active_canvas.canvas;b=a.getBoundingClientRect();g=c=-20;b&&(c-=b.left,g-=b.top);event?(f.style.left=event.pageX+c+"px",f.style.top=event.pageY+g+"px"):(f.style.left=0.5*a.width+c+"px",f.style.top=0.5*a.height+ +g+"px");f.querySelector("button").addEventListener("click",e);a.parentNode.appendChild(f)};LGraphCanvas.prototype.showEditPropertyValue=function(a,b,c){function g(){d(v.value)}function d(c){"number"==typeof a.properties[b]&&(c=Number(c));a.properties[b]=c;if(a.onPropertyChanged)a.onPropertyChanged(b,c);q.parentNode.removeChild(q);a.setDirtyCanvas(!0,!0)}if(a&&void 0!==a.properties[b]){c=c||{};var e="string";null!==a.properties[b]&&(e=typeof a.properties[b]);var f=null;a.getPropertyInfo&&(f=a.getPropertyInfo(b)); +if(a.properties_info)for(var h=0;h";else if("enum"==e&&f.values){l=""}else"boolean"== +e&&(l="");var q=document.createElement("div");q.className="graphdialog";q.innerHTML=""+b+""+l+"";if("enum"==e&&f.values){var v=q.querySelector("select");v.addEventListener("change",function(a){d(a.target.value)})}else if("boolean"==e)(v=q.querySelector("input"))&&v.addEventListener("click",function(a){d(!!v.checked)});else if(v=q.querySelector("input"))v.value=void 0!==a.properties[b]? +a.properties[b]:"",v.addEventListener("keydown",function(a){13==a.keyCode&&(g(),a.preventDefault(),a.stopPropagation())});e=this.canvas.getClientRects()[0];h=f=-20;e&&(f-=e.left,h-=e.top);c.position?(f+=c.position[0],h+=c.position[1]):c.event?(f+=c.event.pageX,h+=c.event.pageY):(f+=0.5*this.canvas.width,h+=0.5*this.canvas.height);q.style.left=f+"px";q.style.top=h+"px";q.querySelector("button").addEventListener("click",g);this.canvas.parentNode.appendChild(q)}};LGraphCanvas.onMenuNodeCollapse=function(a, +b,c,g,d){d.flags.collapsed=!d.flags.collapsed;d.setDirtyCanvas(!0,!0)};LGraphCanvas.onMenuNodePin=function(a,b,c,g,d){d.pin()};LGraphCanvas.onMenuNodeMode=function(a,b,c,g,d){new l.ContextMenu(["Always","On Event","Never"],{event:c,callback:function(a){if(d)switch(a){case "On Event":d.mode=l.ON_EVENT;break;case "Never":d.mode=l.NEVER;break;default:d.mode=l.ALWAYS}},parentMenu:prev_menu,node:d});return!1};LGraphCanvas.onMenuNodeColors=function(a,b,c,g,d){if(!d)throw"no node for color";b=[];for(var e in LGraphCanvas.node_colors)a= +LGraphCanvas.node_colors[e],a={value:e,content:""+e+""},b.push(a);new l.ContextMenu(b,{event:c,callback:function(a){d&&(a=LGraphCanvas.node_colors[a.value])&&(d.color=a.color,d.bgcolor=a.bgcolor,d.setDirtyCanvas(!0))},parentMenu:g,node:d});return!1};LGraphCanvas.onMenuNodeShapes=function(a,b,c,g,d){if(!d)throw"no node passed";new l.ContextMenu(["box","round"],{event:c,callback:function(a){d&&(d.shape=a,d.setDirtyCanvas(!0))}, +parentMenu:g,node:d});return!1};LGraphCanvas.onMenuNodeRemove=function(a,b,c,g,d){if(!d)throw"no node passed";!1!=d.removable&&(d.graph.remove(d),d.setDirtyCanvas(!0,!0))};LGraphCanvas.onMenuNodeClone=function(a,b,c,g,d){!1!=d.clonable&&(a=d.clone())&&(a.pos=[d.pos[0]+5,d.pos[1]+5],d.graph.add(a),d.setDirtyCanvas(!0,!0))};LGraphCanvas.node_colors={red:{color:"#FAA",bgcolor:"#A44"},green:{color:"#AFA",bgcolor:"#4A4"},blue:{color:"#AAF",bgcolor:"#44A"},white:{color:"#FFF",bgcolor:"#AAA"}};LGraphCanvas.prototype.getCanvasMenuOptions= +function(){var a=null;this.getMenuOptions?a=this.getMenuOptions():(a=[{content:"Add Node",has_submenu:!0,callback:LGraphCanvas.onMenuAdd}],this._graph_stack&&0a[2]&&(a[2]=b);ca[3]&&(a[3]=c)};l.isInsideBounding= +function(a,b){return a[0]b[1][0]||a[1]>b[1][1]?!1:!0};l.overlapBounding=h;l.hex2num=function(a){"#"==a.charAt(0)&&(a=a.slice(1));a=a.toUpperCase();for(var b=Array(3),c=0,d,e,f=0;6>f;f+=2)d="0123456789ABCDEF".indexOf(a.charAt(f)),e="0123456789ABCDEF".indexOf(a.charAt(f+1)),b[c]=16*d+e,c++;return b};l.num2hex=function(a){for(var b="#",c,d,e=0;3>e;e++)c=a[e]/16,d=a[e]%16,b+="0123456789ABCDEF".charAt(c)+"0123456789ABCDEF".charAt(d);return b};n.prototype.addItem=function(a, +b,c){function d(a){var b=this.value;b&&b.has_submenu&&e.call(this,a)}function e(a){var b=this.value,d=!0;f.current_submenu&&f.current_submenu.close(a);if(c.callback){var g=c.callback.call(this,b,c,a,f,c.node);!0===g&&(d=!1)}if(b&&(b.callback&&!c.ignore_item_callbacks&&!0!==b.disabled&&(g=b.callback.call(this,b,c,a,f,c.node),!0===g&&(d=!1)),b.submenu)){if(!b.submenu.options)throw"ContextMenu submenu needs options";new f.constructor(b.submenu.options,{callback:b.submenu.callback,event:a,parentMenu:f, +ignore_item_callbacks:b.submenu.ignore_item_callbacks,title:b.submenu.title,autoopen:c.autoopen});d=!1}d&&!f.lock&&f.close()}var f=this;c=c||{};var h=document.createElement("div");h.className="litemenu-entry submenu";var l=!1;if(null===b)h.classList.add("separator");else{h.innerHTML=b&&b.title?b.title:a;if(h.value=b)b.disabled&&(l=!0,h.classList.add("disabled")),(b.submenu||b.has_submenu)&&h.classList.add("has_submenu");"function"==typeof b?(h.dataset.value=a,h.onclick_callback=b):h.dataset.value= +b}this.root.appendChild(h);l||h.addEventListener("click",e);c.autoopen&&h.addEventListener("mouseenter",d);return h};n.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&&!n.isCursorOverElement(a,this.parentMenu.root)&&n.trigger(this.parentMenu.root,"mouseleave",a));this.current_submenu&&this.current_submenu.close(a,!0)};n.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};n.prototype.getTopMenu=function(){return this.options.parentMenu?this.options.parentMenu.getTopMenu():this};n.prototype.getFirstEvent=function(){return this.options.parentMenu?this.options.parentMenu.getFirstEvent():this.options.event};n.isCursorOverElement=function(a,b){var c=a.pageX,d=a.pageY,e=b.getBoundingClientRect(); +return e?d>e.top&&de.left&&ca.canvasY-this.pos[1]||LiteGraph.distance([a.canvasX,a.canvasY],[this.pos[0]+this.center[0],this.pos[1]+this.center[1]])>this.radius)return!1;this.oldmouse=[a.canvasX-this.pos[0],a.canvasY-this.pos[1]];this.captureInput(!0);return!0}};e.prototype.onMouseMove=function(a){if(this.oldmouse){a=[a.canvasX-this.pos[0],a.canvasY-this.pos[1]];var c=this.value,c=c-0.01*(a[1]-this.oldmouse[1]);1c&&(c=0);this.value=c;this.properties.value= -this.properties.min+(this.properties.max-this.properties.min)*this.value;this.oldmouse=a;this.setDirtyCanvas(!0)}};e.prototype.onMouseUp=function(a){this.oldmouse&&(this.oldmouse=null,this.captureInput(!1))};e.prototype.onMouseLeave=function(a){};e.prototype.onWidget=function(a,c){if("increase"==c.name)this.onPropertyChanged("size",this.properties.size+10);else if("decrease"==c.name)this.onPropertyChanged("size",this.properties.size-10)};e.prototype.onPropertyChanged=function(a,c){if("wcolor"==a)this.properties[a]= -c;else if("size"==a)c=parseInt(c),this.properties[a]=c,this.size=[c+4,c+24],this.setDirtyCanvas(!0,!0);else if("min"==a||"max"==a||"value"==a)this.properties[a]=parseFloat(c);else return!1;return!0};LiteGraph.registerNodeType("widget/knob",e);f.title="H.Slider";f.desc="Linear slider controller";f.prototype.onInit=function(){this.value=0.5;this.imgfg=this.loadImage("imgs/slider_fg.png")};f.prototype.onDrawVectorial=function(a){this.imgfg&&this.imgfg.width&&(a.lineWidth=1,a.strokeStyle=this.mouseOver? +a=d)}},enumerable:!0});Object.defineProperty(this.properties,"type",{get:function(){return b.outputs[0].type},set:function(d){b.outputs[0].type=d;b.graph&&b.graph.changeGlobalInputType(a,b.outputs[0].type)},enumerable:!0})}function h(){var a="output_"+(1E3*Math.random()).toFixed();this.addInput(a,null);this.properties={name:a,type:null};var b=this;Object.defineProperty(this.properties,"name",{get:function(){return a},set:function(d){if(""!=d){var e=b.getInputInfo(0);e.name!=d&&(e.name=d,b.graph&& +b.graph.renameGlobalOutput(a,d),a=d)}},enumerable:!0});Object.defineProperty(this.properties,"type",{get:function(){return b.inputs[0].type},set:function(d){b.inputs[0].type=d;b.graph&&b.graph.changeGlobalInputType(a,b.inputs[0].type)},enumerable:!0})}function n(){this.addOutput("value","number");this.addProperty("value",1);this.editable={property:"value",type:"number"}}function l(){this.size=[60,20];this.addInput("value",0,{label:""});this.addOutput("value",0,{label:""});this.addProperty("value", +"")}function a(){this.mode=LiteGraph.ON_EVENT;this.size=[60,20];this.addProperty("msg","");this.addInput("log",LiteGraph.EVENT);this.addInput("msg",0)}function b(){this.size=[60,20];this.addProperty("onExecute","");this.addInput("in","");this.addInput("in2","");this.addOutput("out","");this.addOutput("out2","");this._func=null}e.title="Time";e.desc="Time";e.prototype.onExecute=function(){this.setOutputData(0,1E3*this.graph.globaltime);this.setOutputData(1,this.graph.globaltime)};LiteGraph.registerNodeType("basic/time", +e);d.title="Subgraph";d.desc="Graph inside a node";d.prototype.onSubgraphNewGlobalInput=function(a,b){this.addInput(a,b)};d.prototype.onSubgraphRenamedGlobalInput=function(a,b){var d=this.findInputSlot(a);-1!=d&&(this.getInputInfo(d).name=b)};d.prototype.onSubgraphTypeChangeGlobalInput=function(a,b){var d=this.findInputSlot(a);-1!=d&&(this.getInputInfo(d).type=b)};d.prototype.onSubgraphNewGlobalOutput=function(a,b){this.addOutput(a,b)};d.prototype.onSubgraphRenamedGlobalOutput=function(a,b){var d= +this.findOutputSlot(a);-1!=d&&(this.getOutputInfo(d).name=b)};d.prototype.onSubgraphTypeChangeGlobalOutput=function(a,b){var d=this.findOutputSlot(a);-1!=d&&(this.getOutputInfo(d).type=b)};d.prototype.getExtraMenuOptions=function(a){var b=this;return[{content:"Open",callback:function(){a.openSubgraph(b.subgraph)}}]};d.prototype.onExecute=function(){if(this.inputs)for(var a=0;aa.canvasY-this.pos[1]||LiteGraph.distance([a.canvasX,a.canvasY],[this.pos[0]+this.center[0],this.pos[1]+this.center[1]])>this.radius)return!1;this.oldmouse=[a.canvasX-this.pos[0],a.canvasY-this.pos[1]];this.captureInput(!0);return!0}};d.prototype.onMouseMove=function(a){if(this.oldmouse){a=[a.canvasX-this.pos[0],a.canvasY-this.pos[1]];var b=this.value,b=b-0.01*(a[1]-this.oldmouse[1]);1b&&(b=0);this.value=b;this.properties.value= +this.properties.min+(this.properties.max-this.properties.min)*this.value;this.oldmouse=a;this.setDirtyCanvas(!0)}};d.prototype.onMouseUp=function(a){this.oldmouse&&(this.oldmouse=null,this.captureInput(!1))};d.prototype.onMouseLeave=function(a){};d.prototype.onWidget=function(a,b){if("increase"==b.name)this.onPropertyChanged("size",this.properties.size+10);else if("decrease"==b.name)this.onPropertyChanged("size",this.properties.size-10)};d.prototype.onPropertyChanged=function(a,b){if("wcolor"==a)this.properties[a]= +b;else if("size"==a)b=parseInt(b),this.properties[a]=b,this.size=[b+4,b+24],this.setDirtyCanvas(!0,!0);else if("min"==a||"max"==a||"value"==a)this.properties[a]=parseFloat(b);else return!1;return!0};LiteGraph.registerNodeType("widget/knob",d);f.title="H.Slider";f.desc="Linear slider controller";f.prototype.onInit=function(){this.value=0.5;this.imgfg=this.loadImage("imgs/slider_fg.png")};f.prototype.onDrawVectorial=function(a){this.imgfg&&this.imgfg.width&&(a.lineWidth=1,a.strokeStyle=this.mouseOver? "#FFF":"#AAA",a.fillStyle="#000",a.beginPath(),a.rect(2,0,this.size[0]-4,20),a.stroke(),a.fillStyle=this.properties.wcolor,a.beginPath(),a.rect(2+(this.size[0]-4-20)*this.value,0,20,20),a.fill())};f.prototype.onDrawImage=function(a){this.imgfg&&this.imgfg.width&&(a.lineWidth=1,a.fillStyle="#000",a.fillRect(2,9,this.size[0]-4,2),a.strokeStyle="#333",a.beginPath(),a.moveTo(2,9),a.lineTo(this.size[0]-4,9),a.stroke(),a.strokeStyle="#AAA",a.beginPath(),a.moveTo(2,11),a.lineTo(this.size[0]-4,11),a.stroke(), a.drawImage(this.imgfg,2+(this.size[0]-4)*this.value-0.5*this.imgfg.width,0.5*-this.imgfg.height+10))};f.prototype.onDrawForeground=function(a){this.onDrawImage(a)};f.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])};f.prototype.onMouseDown=function(a){if(0>a.canvasY-this.pos[1])return!1;this.oldmouse=[a.canvasX- -this.pos[0],a.canvasY-this.pos[1]];this.captureInput(!0);return!0};f.prototype.onMouseMove=function(a){if(this.oldmouse){a=[a.canvasX-this.pos[0],a.canvasY-this.pos[1]];var c=this.value,c=c+(a[0]-this.oldmouse[0])/this.size[0];1c&&(c=0);this.value=c;this.oldmouse=a;this.setDirtyCanvas(!0)}};f.prototype.onMouseUp=function(a){this.oldmouse=null;this.captureInput(!1)};f.prototype.onMouseLeave=function(a){};f.prototype.onPropertyChanged=function(a,c){if("wcolor"==a)this.properties[a]=c;else return!1; -return!0};LiteGraph.registerNodeType("widget/hslider",f);k.title="Progress";k.desc="Shows data in linear progress";k.prototype.onExecute=function(){var a=this.getInputData(0);void 0!=a&&(this.properties.value=a)};k.prototype.onDrawForeground=function(a){a.lineWidth=1;a.fillStyle=this.properties.wcolor;var c=(this.properties.value-this.properties.min)/(this.properties.max-this.properties.min),c=Math.min(1,c),c=Math.max(0,c);a.fillRect(2,2,(this.size[0]-4)*c,this.size[1]-4)};LiteGraph.registerNodeType("widget/progress", -k);h.title="Text";h.desc="Shows the input value";h.widgets=[{name:"resize",text:"Resize box",type:"button"},{name:"led_text",text:"LED",type:"minibutton"},{name:"normal_text",text:"Normal",type:"minibutton"}];h.prototype.onDrawForeground=function(a){a.fillStyle=this.properties.color;var c=this.properties.value;this.properties.glowSize?(a.shadowColor=this.properties.color,a.shadowOffsetX=0,a.shadowOffsetY=0,a.shadowBlur=this.properties.glowSize):a.shadowColor="transparent";var d=this.properties.fontsize; -a.textAlign=this.properties.align;a.font=d.toString()+"px "+this.properties.font;this.str="number"==typeof c?c.toFixed(this.properties.decimals):c;if("string"==typeof this.str){var c=this.str.split("\\n"),e;for(e in c)a.fillText(c[e],"left"==this.properties.align?15:this.size[0]-15,-0.15*d+d*(parseInt(e)+1))}a.shadowColor="transparent";this.last_ctx=a;a.textAlign="left"};h.prototype.onExecute=function(){var a=this.getInputData(0);this.properties.value=null!=a?a:"";this.setDirtyCanvas(!0)};h.prototype.resize= -function(){if(this.last_ctx){var a=this.str.split("\\n");this.last_ctx.font=this.properties.fontsize+"px "+this.properties.font;var c=0,d;for(d in a){var e=this.last_ctx.measureText(a[d]).width;ck;k++)if(f[k]){d=f[k];f=this.xbox_mapping;f||(f=this.xbox_mapping= -{axes:[],buttons:{},hat:""});f.axes.lx=d.axes[0];f.axes.ly=d.axes[1];f.axes.rx=d.axes[2];f.axes.ry=d.axes[3];f.axes.ltrigger=d.buttons[6].value;f.axes.rtrigger=d.buttons[7].value;for(k=0;k","string",{values:q.values}); -this.size=[60,40]}function p(){this.addInput("inc","number");this.addOutput("total","number");this.addProperty("increment",1);this.addProperty("value",0)}function s(){this.addInput("v","number");this.addOutput("sin","number");this.addProperty("amplitude",1);this.addProperty("offset",0);this.bgImageUrl="nodes/imgs/icon-sin.png"}function w(){this.addInput("vec2","vec2");this.addOutput("x","number");this.addOutput("y","number")}function x(){this.addInputs([["x","number"],["y","number"]]);this.addOutput("vec2", +this.pos[0],a.canvasY-this.pos[1]];this.captureInput(!0);return!0};f.prototype.onMouseMove=function(a){if(this.oldmouse){a=[a.canvasX-this.pos[0],a.canvasY-this.pos[1]];var b=this.value,b=b+(a[0]-this.oldmouse[0])/this.size[0];1b&&(b=0);this.value=b;this.oldmouse=a;this.setDirtyCanvas(!0)}};f.prototype.onMouseUp=function(a){this.oldmouse=null;this.captureInput(!1)};f.prototype.onMouseLeave=function(a){};f.prototype.onPropertyChanged=function(a,b){if("wcolor"==a)this.properties[a]=b;else return!1; +return!0};LiteGraph.registerNodeType("widget/hslider",f);h.title="Progress";h.desc="Shows data in linear progress";h.prototype.onExecute=function(){var a=this.getInputData(0);void 0!=a&&(this.properties.value=a)};h.prototype.onDrawForeground=function(a){a.lineWidth=1;a.fillStyle=this.properties.wcolor;var b=(this.properties.value-this.properties.min)/(this.properties.max-this.properties.min),b=Math.min(1,b),b=Math.max(0,b);a.fillRect(2,2,(this.size[0]-4)*b,this.size[1]-4)};LiteGraph.registerNodeType("widget/progress", +h);n.title="Text";n.desc="Shows the input value";n.widgets=[{name:"resize",text:"Resize box",type:"button"},{name:"led_text",text:"LED",type:"minibutton"},{name:"normal_text",text:"Normal",type:"minibutton"}];n.prototype.onDrawForeground=function(a){a.fillStyle=this.properties.color;var b=this.properties.value;this.properties.glowSize?(a.shadowColor=this.properties.color,a.shadowOffsetX=0,a.shadowOffsetY=0,a.shadowBlur=this.properties.glowSize):a.shadowColor="transparent";var c=this.properties.fontsize; +a.textAlign=this.properties.align;a.font=c.toString()+"px "+this.properties.font;this.str="number"==typeof b?b.toFixed(this.properties.decimals):b;if("string"==typeof this.str){var b=this.str.split("\\n"),d;for(d in b)a.fillText(b[d],"left"==this.properties.align?15:this.size[0]-15,-0.15*c+c*(parseInt(d)+1))}a.shadowColor="transparent";this.last_ctx=a;a.textAlign="left"};n.prototype.onExecute=function(){var a=this.getInputData(0);this.properties.value=null!=a?a:"";this.setDirtyCanvas(!0)};n.prototype.resize= +function(){if(this.last_ctx){var a=this.str.split("\\n");this.last_ctx.font=this.properties.fontsize+"px "+this.properties.font;var b=0,c;for(c in a){var d=this.last_ctx.measureText(a[c]).width;bh;h++)if(e[h]){d=e[h];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(h=0;h","string",{values:w.values}); +this.size=[60,40]}function p(){this.addInput("inc","number");this.addOutput("total","number");this.addProperty("increment",1);this.addProperty("value",0)}function q(){this.addInput("v","number");this.addOutput("sin","number");this.addProperty("amplitude",1);this.addProperty("offset",0);this.bgImageUrl="nodes/imgs/icon-sin.png"}function v(){this.addInput("vec2","vec2");this.addOutput("x","number");this.addOutput("y","number")}function x(){this.addInputs([["x","number"],["y","number"]]);this.addOutput("vec2", "vec2");this.properties={x:0,y:0};this._data=new Float32Array(2)}function y(){this.addInput("vec3","vec3");this.addOutput("x","number");this.addOutput("y","number");this.addOutput("z","number")}function z(){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 A(){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)}d.title="Converter";d.desc="type A to type B";d.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))};LiteGraph.registerNodeType("math/average",t);u.values="+-*/%^".split("");u.title="Operation";u.desc="Easy math operators";u["@OP"]={type:"enum",title:"operation",values:u.values};u.prototype.setValue=function(a){"string"==typeof a&&(a=parseFloat(a));this.properties.value=a};u.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)};u.prototype.onDrawBackground=function(a){this.flags.collapsed||(a.font="40px Arial",a.fillStyle="black",a.textAlign="center",a.fillText(this.properties.OP,0.5*this.size[0],0.5* -this.size[1]+LiteGraph.NODE_TITLE_HEIGHT),a.textAlign="left")};LiteGraph.registerNodeType("math/operation",u);n.title="Compare";n.desc="compares between two values";n.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)}}};n.prototype.onGetOutputs=function(){return[["A==B","boolean"],["A!=B","boolean"],["A>B","boolean"],["A=B","boolean"],["A<=B","boolean"]]};LiteGraph.registerNodeType("math/compare",n);q.values="> < == != <= >=".split(" ");q["@OP"]={type:"enum",title:"operation",values:q.values};q.title="Condition";q.desc="evaluates condition between A and B"; -q.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)};LiteGraph.registerNodeType("math/condition",q);p.title="Accumulate";p.desc="Increments a value every time";p.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)};LiteGraph.registerNodeType("math/accumulate",p);s.title="Trigonometry";s.desc="Sin Cos Tan";s.filter="shader";s.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,g=this.outputs.length;cXY";w.desc="vector 2 to components";w.prototype.onExecute=function(){var a= -this.getInputData(0);null!=a&&(this.setOutputData(0,a[0]),this.setOutputData(1,a[1]))};LiteGraph.registerNodeType("math3d/vec2-to-xyz",w);x.title="XY->Vec2";x.desc="components to vector2";x.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)};LiteGraph.registerNodeType("math3d/xy-to-vec2",x);y.title="Vec3->XYZ";y.desc="vector 3 to components";y.prototype.onExecute= +"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)}e.title="Converter";e.desc="type A to type B";e.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))};LiteGraph.registerNodeType("math/average",u);t.values="+-*/%^".split("");t.title="Operation";t.desc="Easy math operators";t["@OP"]={type:"enum",title:"operation",values:t.values};t.prototype.setValue=function(a){"string"==typeof a&&(a=parseFloat(a));this.properties.value=a};t.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)};t.prototype.onDrawBackground=function(a){this.flags.collapsed||(a.font="40px Arial",a.fillStyle="black",a.textAlign="center",a.fillText(this.properties.OP,0.5*this.size[0],0.5* +this.size[1]+LiteGraph.NODE_TITLE_HEIGHT),a.textAlign="left")};LiteGraph.registerNodeType("math/operation",t);r.title="Compare";r.desc="compares between two values";r.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)}}};r.prototype.onGetOutputs=function(){return[["A==B","boolean"],["A!=B","boolean"],["A>B","boolean"],["A=B","boolean"],["A<=B","boolean"]]};LiteGraph.registerNodeType("math/compare",r);w.values="> < == != <= >=".split(" ");w["@OP"]={type:"enum",title:"operation",values:w.values};w.title="Condition";w.desc="evaluates condition between A and B"; +w.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)};LiteGraph.registerNodeType("math/condition",w);p.title="Accumulate";p.desc="Increments a value every time";p.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)};LiteGraph.registerNodeType("math/accumulate",p);q.title="Trigonometry";q.desc="Sin Cos Tan";q.filter="shader";q.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,e=this.outputs.length;cXY";v.desc="vector 2 to components";v.prototype.onExecute=function(){var a= +this.getInputData(0);null!=a&&(this.setOutputData(0,a[0]),this.setOutputData(1,a[1]))};LiteGraph.registerNodeType("math3d/vec2-to-xyz",v);x.title="XY->Vec2";x.desc="components to vector2";x.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)};LiteGraph.registerNodeType("math3d/xy-to-vec2",x);y.title="Vec3->XYZ";y.desc="vector 3 to components";y.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]))};LiteGraph.registerNodeType("math3d/vec3-to-xyz",y);z.title="XYZ->Vec3";z.desc="components to vector3";z.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)}; LiteGraph.registerNodeType("math3d/xyz-to-vec3",z);A.title="Vec4->XYZW";A.desc="vector 4 to components";A.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]))};LiteGraph.registerNodeType("math3d/vec4-to-xyzw",A);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 g=this._data;g[0]=a;g[1]=b;g[2]=c;g[3]=d;this.setOutputData(0,g)};LiteGraph.registerNodeType("math3d/xyzw-to-vec4",B);window.glMatrix&&(r=function(){this.addOutput("quat","quat");this.properties={x:0,y:0,z:0,w:1};this._value=quat.create()},r.title="Quaternion",r.desc="quaternion",r.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",r),r=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()},r.title="Rotation",r.desc="quaternion rotation",r.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)},LiteGraph.registerNodeType("math3d/rotation",r),r=function(){this.addInputs([["vec3","vec3"],["quat","quat"]]);this.addOutput("result","vec3");this.properties={vec:[0,0,1]}},r.title="Rot. Vec3",r.desc="rotate a point",r.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))},LiteGraph.registerNodeType("math3d/rotate_vec3",r),r=function(){this.addInputs([["A","quat"],["B","quat"]]);this.addOutput("A*B","quat");this._value=quat.create()},r.title="Mult. Quat",r.desc="rotate quaternion",r.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))}},LiteGraph.registerNodeType("math3d/mult-quat",r),r=function(){this.addInputs([["A","quat"],["B","quat"],["factor", -"number"]]);this.addOutput("slerp","quat");this.addProperty("factor",0.5);this._value=quat.create()},r.title="Quat Slerp",r.desc="quaternion spherical interpolation",r.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)}}},LiteGraph.registerNodeType("math3d/quat-slerp",r))})(); -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 d=this.getInputData(0);if(void 0!==d){for(var e=1;eb&&(b=0);if(0!=a.length){var d=[0,0,0];if(0==b)d=a[0];else if(1==b)d=a[a.length-1];else{var e=(a.length-1)*b,b=a[Math.floor(e)],a=a[Math.floor(e)+1],e=e-Math.floor(e);d[0]=b[0]*(1-e)+a[0]*e;d[1]=b[1]*(1-e)+a[1]*e;d[2]=b[2]*(1-e)+a[2]*e}for(var f in d)d[f]/=255;this.boxcolor=colorToString(d);this.setOutputData(0,d)}};LiteGraph.registerNodeType("color/palette",e);f.title="Frame"; -f.desc="Frame viewerew";f.widgets=[{name:"resize",text:"Resize box",type:"button"},{name:"view",text:"View Image",type:"button"}];f.prototype.onDrawBackground=function(a){this.frame&&a.drawImage(this.frame,0,0,this.size[0],this.size[1])};f.prototype.onExecute=function(){this.frame=this.getInputData(0);this.setDirtyCanvas(!0)};f.prototype.onWidget=function(a,b){if("resize"==b.name&&this.frame){var d=this.frame.width,e=this.frame.height;d||null==this.frame.videoWidth||(d=this.frame.videoWidth,e=this.frame.videoHeight); -d&&e&&(this.size=[d,e]);this.setDirtyCanvas(!0,!0)}else"view"==b.name&&this.show()};f.prototype.show=function(){showElement&&this.frame&&showElement(this.frame)};LiteGraph.registerNodeType("graphics/frame",f);k.title="Image fade";k.desc="Fades between images";k.widgets=[{name:"resizeA",text:"Resize to A",type:"button"},{name:"resizeB",text:"Resize to B",type:"button"}];k.prototype.onAdded=function(){this.createCanvas();var a=this.canvas.getContext("2d");a.fillStyle="#000";a.fillRect(0,0,this.properties.width, -this.properties.height)};k.prototype.createCanvas=function(){this.canvas=document.createElement("canvas");this.canvas.width=this.properties.width;this.canvas.height=this.properties.height};k.prototype.onExecute=function(){var a=this.canvas.getContext("2d");this.canvas.width=this.canvas.width;var b=this.getInputData(0);null!=b&&a.drawImage(b,0,0,this.canvas.width,this.canvas.height);b=this.getInputData(2);null==b?b=this.properties.fade:this.properties.fade=b;a.globalAlpha=b;b=this.getInputData(1); -null!=b&&a.drawImage(b,0,0,this.canvas.width,this.canvas.height);a.globalAlpha=1;this.setOutputData(0,this.canvas);this.setDirtyCanvas(!0)};LiteGraph.registerNodeType("graphics/imagefade",k);h.title="Crop";h.desc="Crop Image";h.prototype.onAdded=function(){this.createCanvas()};h.prototype.createCanvas=function(){this.canvas=document.createElement("canvas");this.canvas.width=this.properties.width;this.canvas.height=this.properties.height};h.prototype.onExecute=function(){var a=this.getInputData(0); -a&&(a.width?(this.canvas.getContext("2d").drawImage(a,-this.properties.x,-this.properties.y,a.width*this.properties.scale,a.height*this.properties.scale),this.setOutputData(0,this.canvas)):this.setOutputData(0,null))};h.prototype.onDrawBackground=function(a){this.flags.collapsed||this.canvas&&a.drawImage(this.canvas,0,0,this.canvas.width,this.canvas.height,0,0,this.size[0],this.size[1])};h.prototype.onPropertyChanged=function(a,b){this.properties[a]=b;"scale"==a?(this.properties[a]=parseFloat(b), -0==this.properties[a]&&(this.trace("Error in scale"),this.properties[a]=1)):this.properties[a]=parseInt(b);this.createCanvas();return!0};LiteGraph.registerNodeType("graphics/cropImage",h);a.title="Video";a.desc="Video playback";a.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"}];a.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 a=this.getInputData(0);a&&0<=a&&1>=a&&(this._video.currentTime=a*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)}};a.prototype.onStart=function(){this.play()};a.prototype.onStop=function(){this.stop()};a.prototype.loadVideo=function(a){this._video_url=a;this._video= -document.createElement("video");this._video.src=a;this._video.type="type=video/mp4";this._video.muted=!0;this._video.autoplay=!0;var b=this;this._video.addEventListener("loadedmetadata",function(a){b.trace("Duration: "+this.duration+" seconds");b.trace("Size: "+this.videoWidth+","+this.videoHeight);b.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);b.trace("Error loading video: "+this.src);if(this.error)switch(this.error.code){case this.error.MEDIA_ERR_ABORTED:b.trace("You stopped the video.");break;case this.error.MEDIA_ERR_NETWORK:b.trace("Network error - please try again later.");break;case this.error.MEDIA_ERR_DECODE:b.trace("Video is broken..");break;case this.error.MEDIA_ERR_SRC_NOT_SUPPORTED:b.trace("Sorry, your browser can't play this video.")}});this._video.addEventListener("ended",function(a){b.trace("Ended.");this.play()})}; -a.prototype.onPropertyChanged=function(a,b){this.properties[a]=b;"url"==a&&""!=b&&this.loadVideo(b);return!0};a.prototype.play=function(){this._video&&this._video.play()};a.prototype.playPause=function(){this._video&&(this._video.paused?this.play():this.pause())};a.prototype.stop=function(){this._video&&(this._video.pause(),this._video.currentTime=0)};a.prototype.pause=function(){this._video&&(this.trace("Video paused"),this._video.pause())};a.prototype.onWidget=function(a,b){};LiteGraph.registerNodeType("graphics/video", -a);b.title="Webcam";b.desc="Webcam image";b.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;navigator.getUserMedia({video:!0},this.streamReady.bind(this),a);var b=this}};b.prototype.onRemoved=function(){this._webcam_stream&& -(this._webcam_stream.stop(),this._video=this._webcam_stream=null)};b.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)})};b.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))};b.prototype.getExtraMenuOptions=function(a){var b=this;return[{content:b.properties.show?"Hide Frame":"Show Frame",callback:function(){b.properties.show=!b.properties.show}}]};b.prototype.onDrawBackground=function(a){this.flags.collapsed||20>=this.size[1]||!this.properties.show||!this._video||(a.save(),a.drawImage(this._video,0,0,this.size[0],this.size[1]),a.restore())};LiteGraph.registerNodeType("graphics/webcam",b)})(); +(function(){function e(){this.inputs=[];this.addOutput("frame","image");this.properties={url:""}}function d(){this.addInput("f","number");this.addOutput("Color","color");this.properties={colorA:"#444444",colorB:"#44AAFF",colorC:"#44FFAA",colorD:"#FFFFFF"}}function f(){this.addInput("","image");this.size=[200,200]}function h(){this.addInputs([["img1","image"],["img2","image"],["fade","number"]]);this.addInput("","image");this.properties={fade:0.5,width:512,height:512}}function n(){this.addInput("", +"image");this.addOutput("","image");this.properties={width:256,height:256,x:0,y:0,scale:1};this.size=[50,20]}function l(){this.addInput("t","number");this.addOutputs([["frame","image"],["t","number"],["d","number"]]);this.properties={url:""}}function a(){this.addOutput("Webcam","image");this.properties={}}e.title="Image";e.desc="Image loader";e.widgets=[{name:"load",text:"Load",type:"button"}];e.supported_extensions=["jpg","jpeg","png","gif"];e.prototype.onAdded=function(){""!=this.properties.url&& +null==this.img&&this.loadImage(this.properties.url)};e.prototype.onDrawBackground=function(a){this.img&&5c&&(c=0);if(0!=a.length){var d=[0,0,0];if(0==c)d=a[0];else if(1==c)d=a[a.length-1];else{var e=(a.length-1)*c,c=a[Math.floor(e)],a=a[Math.floor(e)+1],e=e-Math.floor(e);d[0]=c[0]*(1-e)+a[0]*e;d[1]=c[1]*(1-e)+a[1]*e;d[2]=c[2]*(1-e)+a[2]*e}for(var f in d)d[f]/=255;this.boxcolor=colorToString(d);this.setOutputData(0,d)}};LiteGraph.registerNodeType("color/palette",d);f.title="Frame"; +f.desc="Frame viewerew";f.widgets=[{name:"resize",text:"Resize box",type:"button"},{name:"view",text:"View Image",type:"button"}];f.prototype.onDrawBackground=function(a){this.frame&&a.drawImage(this.frame,0,0,this.size[0],this.size[1])};f.prototype.onExecute=function(){this.frame=this.getInputData(0);this.setDirtyCanvas(!0)};f.prototype.onWidget=function(a,c){if("resize"==c.name&&this.frame){var d=this.frame.width,e=this.frame.height;d||null==this.frame.videoWidth||(d=this.frame.videoWidth,e=this.frame.videoHeight); +d&&e&&(this.size=[d,e]);this.setDirtyCanvas(!0,!0)}else"view"==c.name&&this.show()};f.prototype.show=function(){showElement&&this.frame&&showElement(this.frame)};LiteGraph.registerNodeType("graphics/frame",f);h.title="Image fade";h.desc="Fades between images";h.widgets=[{name:"resizeA",text:"Resize to A",type:"button"},{name:"resizeB",text:"Resize to B",type:"button"}];h.prototype.onAdded=function(){this.createCanvas();var a=this.canvas.getContext("2d");a.fillStyle="#000";a.fillRect(0,0,this.properties.width, +this.properties.height)};h.prototype.createCanvas=function(){this.canvas=document.createElement("canvas");this.canvas.width=this.properties.width;this.canvas.height=this.properties.height};h.prototype.onExecute=function(){var a=this.canvas.getContext("2d");this.canvas.width=this.canvas.width;var c=this.getInputData(0);null!=c&&a.drawImage(c,0,0,this.canvas.width,this.canvas.height);c=this.getInputData(2);null==c?c=this.properties.fade:this.properties.fade=c;a.globalAlpha=c;c=this.getInputData(1); +null!=c&&a.drawImage(c,0,0,this.canvas.width,this.canvas.height);a.globalAlpha=1;this.setOutputData(0,this.canvas);this.setDirtyCanvas(!0)};LiteGraph.registerNodeType("graphics/imagefade",h);n.title="Crop";n.desc="Crop Image";n.prototype.onAdded=function(){this.createCanvas()};n.prototype.createCanvas=function(){this.canvas=document.createElement("canvas");this.canvas.width=this.properties.width;this.canvas.height=this.properties.height};n.prototype.onExecute=function(){var a=this.getInputData(0); +a&&(a.width?(this.canvas.getContext("2d").drawImage(a,-this.properties.x,-this.properties.y,a.width*this.properties.scale,a.height*this.properties.scale),this.setOutputData(0,this.canvas)):this.setOutputData(0,null))};n.prototype.onDrawBackground=function(a){this.flags.collapsed||this.canvas&&a.drawImage(this.canvas,0,0,this.canvas.width,this.canvas.height,0,0,this.size[0],this.size[1])};n.prototype.onPropertyChanged=function(a,c){this.properties[a]=c;"scale"==a?(this.properties[a]=parseFloat(c), +0==this.properties[a]&&(this.trace("Error in scale"),this.properties[a]=1)):this.properties[a]=parseInt(c);this.createCanvas();return!0};LiteGraph.registerNodeType("graphics/cropImage",n);l.title="Video";l.desc="Video playback";l.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"}];l.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 a=this.getInputData(0);a&&0<=a&&1>=a&&(this._video.currentTime=a*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)}};l.prototype.onStart=function(){this.play()};l.prototype.onStop=function(){this.stop()};l.prototype.loadVideo=function(a){this._video_url=a;this._video= +document.createElement("video");this._video.src=a;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()})}; +l.prototype.onPropertyChanged=function(a,c){this.properties[a]=c;"url"==a&&""!=c&&this.loadVideo(c);return!0};l.prototype.play=function(){this._video&&this._video.play()};l.prototype.playPause=function(){this._video&&(this._video.paused?this.play():this.pause())};l.prototype.stop=function(){this._video&&(this._video.pause(),this._video.currentTime=0)};l.prototype.pause=function(){this._video&&(this.trace("Video paused"),this._video.pause())};l.prototype.onWidget=function(a,c){};LiteGraph.registerNodeType("graphics/video", +l);a.title="Webcam";a.desc="Webcam image";a.prototype.openStream=function(){function a(b){console.log("Webcam rejected",b);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),a);var c=this}};a.prototype.onRemoved=function(){this._webcam_stream&& +(this._webcam_stream.stop(),this._video=this._webcam_stream=null)};a.prototype.streamReady=function(a){this._webcam_stream=a;var c=this._video;c||(c=document.createElement("video"),c.autoplay=!0,c.src=window.URL.createObjectURL(a),this._video=c,c.onloadedmetadata=function(a){console.log(a)})};a.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))};a.prototype.getExtraMenuOptions=function(a){var c=this;return[{content:c.properties.show?"Hide Frame":"Show Frame",callback:function(){c.properties.show=!c.properties.show}}]};a.prototype.onDrawBackground=function(a){this.flags.collapsed||20>=this.size[1]||!this.properties.show||!this._video||(a.save(),a.drawImage(this._video,0,0,this.size[0],this.size[1]),a.restore())};LiteGraph.registerNodeType("graphics/webcam",a)})(); if("undefined"!=typeof LiteGraph){var LGraphTexture=function(){this.addOutput("Texture","Texture");this.properties={name:"",filter:!0};this.size=[LGraphTexture.image_preview_size,LGraphTexture.image_preview_size]};LGraphTexture.title="Texture";LGraphTexture.desc="Texture";LGraphTexture.widgets_info={name:{widget:"texture"},filter:{widget:"checkbox"}};LGraphTexture.loadTextureCallback=null;LGraphTexture.image_preview_size=256;LGraphTexture.PASS_THROUGH=1;LGraphTexture.COPY=2;LGraphTexture.LOW=3;LGraphTexture.HIGH= -4;LGraphTexture.REUSE=5;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};LGraphTexture.getTexturesContainer=function(){return gl.textures};LGraphTexture.loadTexture=function(d,e){e=e||{};var f=d;"http://"==f.substr(0,7)&&LiteGraph.proxy&&(f=LiteGraph.proxy+f.substr(7));return LGraphTexture.getTexturesContainer()[d]=GL.Texture.fromURL(f, -e)};LGraphTexture.getTexture=function(d){var e=this.getTexturesContainer();if(!e)throw"Cannot load texture, container of textures not found";e=e[d];return!e&&d&&":"!=d[0]?this.loadTexture(d):e};LGraphTexture.getTargetTexture=function(d,e,f){if(!d)throw"LGraphTexture.getTargetTexture expects a reference texture";var k=null;switch(f){case LGraphTexture.LOW:k=gl.UNSIGNED_BYTE;break;case LGraphTexture.HIGH:k=gl.HIGH_PRECISION_FORMAT;break;case LGraphTexture.REUSE:return d;default:k=d?d.type:gl.UNSIGNED_BYTE}e&& -e.width==d.width&&e.height==d.height&&e.type==k||(e=new GL.Texture(d.width,d.height,{type:k,format:gl.RGBA,filter:gl.LINEAR}));return e};LGraphTexture.getNoiseTexture=function(){if(this._noise_texture)return this._noise_texture;for(var d=new Uint8Array(1048576),e=0;1048576>e;++e)d[e]=255*Math.random();return this._noise_texture=d=GL.Texture.fromMemory(512,512,d,{format:gl.RGBA,wrap:gl.REPEAT,filter:gl.NEAREST})};LGraphTexture.prototype.onDropFile=function(d,e,f){if(d){var k=null;"string"==typeof d? -k=GL.Texture.fromURL(d):-1!=e.toLowerCase().indexOf(".dds")?k=GL.Texture.fromDDSInMemory(d):(d=new Blob([f]),d=URL.createObjectURL(d),k=GL.Texture.fromURL(d));this._drop_texture=k;this.properties.name=e}else this._drop_texture=null,this.properties.name=""};LGraphTexture.prototype.getExtraMenuOptions=function(d){var e=this;if(this._drop_texture)return[{content:"Clear",callback:function(){e._drop_texture=null;e.properties.name=""}}]};LGraphTexture.prototype.onExecute=function(){var d=null;this.isOutputConnected(1)&& -(d=this.getInputData(0));!d&&this._drop_texture&&(d=this._drop_texture);!d&&this.properties.name&&(d=LGraphTexture.getTexture(this.properties.name));if(d){this._last_tex=d;!1===this.properties.filter?d.setParameter(gl.TEXTURE_MAG_FILTER,gl.NEAREST):d.setParameter(gl.TEXTURE_MAG_FILTER,gl.LINEAR);this.setOutputData(0,d);for(var e=1;e=this.size[1]))if(this._drop_texture&&d.webgl)d.drawImage(this._drop_texture,0,0,this.size[0],this.size[1]);else{if(this._last_preview_tex!=this._last_tex)if(d.webgl)this._canvas=this._last_tex;else{var e=LGraphTexture.generateLowResTexturePreview(this._last_tex);if(!e)return;this._last_preview_tex=this._last_tex; -this._canvas=cloneCanvas(e)}this._canvas&&(d.save(),d.webgl||(d.translate(0,this.size[1]),d.scale(1,-1)),d.drawImage(this._canvas,0,0,this.size[0],this.size[1]),d.restore())}};LGraphTexture.generateLowResTexturePreview=function(d){if(!d)return null;var e=LGraphTexture.image_preview_size,f=d;if(d.format==gl.DEPTH_COMPONENT)return null;if(d.width>e||d.height>e)f=this._preview_temp_tex,this._preview_temp_tex||(this._preview_temp_tex=f=new GL.Texture(e,e,{minFilter:gl.NEAREST})),d.copyTo(f);d=this._preview_canvas; -d||(this._preview_canvas=d=createCanvas(e,e));f&&f.toCanvas(d);return d};LGraphTexture.prototype.getResources=function(d){d[this.properties.name]=GL.Texture;return d};LGraphTexture.prototype.onGetInputs=function(){return[["in","Texture"]]};LGraphTexture.prototype.onGetOutputs=function(){return[["width","number"],["height","number"],["aspect","number"]]};LiteGraph.registerNodeType("texture/texture",LGraphTexture);var LGraphTexturePreview=function(){this.addInput("Texture","Texture");this.properties= -{flipY:!1};this.size=[LGraphTexture.image_preview_size,LGraphTexture.image_preview_size]};LGraphTexturePreview.title="Preview";LGraphTexturePreview.desc="Show a texture in the graph canvas";LGraphTexturePreview.prototype.onDrawBackground=function(d){if(!this.flags.collapsed&&d.webgl){var e=this.getInputData(0);if(e){var f=null,f=!e.handle&&d.webgl?e:LGraphTexture.generateLowResTexturePreview(e);d.save();this.properties.flipY&&(d.translate(0,this.size[1]),d.scale(1,-1));d.drawImage(f,0,0,this.size[0], -this.size[1]);d.restore()}}};LiteGraph.registerNodeType("texture/preview",LGraphTexturePreview);var LGraphTextureSave=function(){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 d=this.getInputData(0);d&&(this.properties.name&&(LGraphTexture.getTexturesContainer()[this.properties.name]=d),this.setOutputData(0,d))};LiteGraph.registerNodeType("texture/save", -LGraphTextureSave);var LGraphTextureOperation=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: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(d){var e=this;return[{content:e.properties.show?"Hide Texture":"Show Texture", -callback:function(){e.properties.show=!e.properties.show}}]};LGraphTextureOperation.prototype.onDrawBackground=function(d){this.flags.collapsed||20>=this.size[1]||!this.properties.show||!this._tex||this._tex.gl!=d||(d.save(),d.drawImage(this._tex,0,0,this.size[0],this.size[1]),d.restore())};LGraphTextureOperation.prototype.onExecute=function(){var d=this.getInputData(0);if(this.isOutputConnected(0))if(this.properties.precision===LGraphTexture.PASS_THROUGH)this.setOutputData(0,d);else{var e=this.getInputData(1); -if(this.properties.uvcode||this.properties.pixelcode){var f=512,k=512;d?(f=d.width,k=d.height):e&&(f=e.width,k=e.height);this._tex=d||this._tex?LGraphTexture.getTargetTexture(d||this._tex,this._tex,this.properties.precision):new GL.Texture(f,k,{type:this.precision===LGraphTexture.LOW?gl.UNSIGNED_BYTE:gl.HIGH_PRECISION_FORMAT,format:gl.RGBA,filter:gl.LINEAR});var h="";this.properties.uvcode&&(h="uv = "+this.properties.uvcode,-1!=this.properties.uvcode.indexOf(";")&&(h=this.properties.uvcode));var a= -"";this.properties.pixelcode&&(a="result = "+this.properties.pixelcode,-1!=this.properties.pixelcode.indexOf(";")&&(a=this.properties.pixelcode));var b=this._shader;if(!b||this._shader_code!=h+"|"+a){try{this._shader=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,LGraphTextureOperation.pixel_shader,{UV_CODE:h,PIXEL_CODE:a}),this.boxcolor="#00FF00"}catch(c){console.log("Error compiling shader: ",c);this.boxcolor="#FF0000";return}this.boxcolor="#FF0000";this._shader_code=h+"|"+a;b=this._shader}if(b){this.boxcolor= -"green";var g=this.getInputData(2);null!=g?this.properties.value=g:g=parseFloat(this.properties.value);var l=this.graph.getTime();this._tex.drawTo(function(){gl.disable(gl.DEPTH_TEST);gl.disable(gl.CULL_FACE);gl.disable(gl.BLEND);d&&d.bind(0);e&&e.bind(1);var a=Mesh.getScreenQuad();b.uniforms({u_texture:0,u_textureB:1,value:g,texSize:[f,k],time:l}).draw(a)});this.setOutputData(0,this._tex)}else this.boxcolor="red"}}};LGraphTextureOperation.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"; -LiteGraph.registerNodeType("texture/operation",LGraphTextureOperation);var LGraphTextureShader=function(){this.addOutput("Texture","Texture");this.properties={code:"",width:512,height:512};this.properties.code="\nvoid main() {\n vec2 uv = coord;\n vec3 color = vec3(0.0);\n//your code here\n\ngl_FragColor = vec4(color, 1.0);\n}\n"};LGraphTextureShader.title="Shader";LGraphTextureShader.desc="Texture shader";LGraphTextureShader.widgets_info={code:{type:"code"},precision:{widget:"combo",values:LGraphTexture.MODE_VALUES}}; -LGraphTextureShader.prototype.onExecute=function(){if(this.isOutputConnected(0)){if(this._shader_code!=this.properties.code)if(this._shader_code=this.properties.code,this._shader=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,LGraphTextureShader.pixel_shader+this.properties.code))this.boxcolor="green";else{this.boxcolor="red";return}this._tex&&this._tex.width==this.properties.width&&this._tex.height==this.properties.height||(this._tex=new GL.Texture(this.properties.width,this.properties.height,{format:gl.RGBA, -filter:gl.LINEAR}));var d=this._tex,e=this._shader,f=this.graph.getTime();d.drawTo(function(){e.uniforms({texSize:[d.width,d.height],time:f}).draw(Mesh.getScreenQuad())});this.setOutputData(0,this._tex)}};LGraphTextureShader.pixel_shader="precision highp float;\n\t\t\t\n\t\t\tvarying vec2 v_coord;\n\t\t\tuniform float time;\n\t\t\t";LiteGraph.registerNodeType("texture/shader",LGraphTextureShader);var LGraphTextureScaleOffset=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: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 d=this.getInputData(0);if(this.isOutputConnected(0)&&d)if(this.properties.precision=== -LGraphTexture.PASS_THROUGH)this.setOutputData(0,d);else{var e=d.width,f=d.height,k=this.precision===LGraphTexture.LOW?gl.UNSIGNED_BYTE:gl.HIGH_PRECISION_FORMAT;this.precision===LGraphTexture.DEFAULT&&(k=d.type);this._tex&&this._tex.width==e&&this._tex.height==f&&this._tex.type==k||(this._tex=new GL.Texture(e,f,{type:k,format:gl.RGBA,filter:gl.LINEAR}));var h=this._shader;h||(h=new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER,LGraphTextureScaleOffset.pixel_shader));var a=this.getInputData(1);a?(this.properties.scale[0]= -a[0],this.properties.scale[1]=a[1]):a=this.properties.scale;var b=this.getInputData(2);b?(this.properties.offset[0]=b[0],this.properties.offset[1]=b[1]):b=this.properties.offset;this._tex.drawTo(function(){gl.disable(gl.DEPTH_TEST);gl.disable(gl.CULL_FACE);gl.disable(gl.BLEND);d.bind(0);var c=Mesh.getScreenQuad();h.uniforms({u_texture:0,u_scale:a,u_offset:b}).draw(c)});this.setOutputData(0,this._tex)}};LGraphTextureScaleOffset.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 u_scale;\n\t\t\tuniform vec2 u_offset;\n\t\t\t\n\t\t\tvoid main() {\n\t\t\t\tvec2 uv = v_coord;\n\t\t\t\tuv = uv / u_scale - u_offset;\n\t\t\t\tgl_FragColor = texture2D(u_texture, uv);\n\t\t\t}\n\t\t\t"; +4;LGraphTexture.REUSE=5;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};LGraphTexture.getTexturesContainer=function(){return gl.textures};LGraphTexture.loadTexture=function(e,d){d=d||{};var f=e;"http://"==f.substr(0,7)&&LiteGraph.proxy&&(f=LiteGraph.proxy+f.substr(7));return LGraphTexture.getTexturesContainer()[e]=GL.Texture.fromURL(f, +d)};LGraphTexture.getTexture=function(e){var d=this.getTexturesContainer();if(!d)throw"Cannot load texture, container of textures not found";d=d[e];return!d&&e&&":"!=e[0]?this.loadTexture(e):d};LGraphTexture.getTargetTexture=function(e,d,f){if(!e)throw"LGraphTexture.getTargetTexture expects a reference texture";var h=null;switch(f){case LGraphTexture.LOW:h=gl.UNSIGNED_BYTE;break;case LGraphTexture.HIGH:h=gl.HIGH_PRECISION_FORMAT;break;case LGraphTexture.REUSE:return e;default:h=e?e.type:gl.UNSIGNED_BYTE}d&& +d.width==e.width&&d.height==e.height&&d.type==h||(d=new GL.Texture(e.width,e.height,{type:h,format:gl.RGBA,filter:gl.LINEAR}));return d};LGraphTexture.getNoiseTexture=function(){if(this._noise_texture)return this._noise_texture;for(var e=new Uint8Array(1048576),d=0;1048576>d;++d)e[d]=255*Math.random();return this._noise_texture=e=GL.Texture.fromMemory(512,512,e,{format:gl.RGBA,wrap:gl.REPEAT,filter:gl.NEAREST})};LGraphTexture.prototype.onDropFile=function(e,d,f){if(e){var h=null;"string"==typeof e? +h=GL.Texture.fromURL(e):-1!=d.toLowerCase().indexOf(".dds")?h=GL.Texture.fromDDSInMemory(e):(e=new Blob([f]),e=URL.createObjectURL(e),h=GL.Texture.fromURL(e));this._drop_texture=h;this.properties.name=d}else this._drop_texture=null,this.properties.name=""};LGraphTexture.prototype.getExtraMenuOptions=function(e){var d=this;if(this._drop_texture)return[{content:"Clear",callback:function(){d._drop_texture=null;d.properties.name=""}}]};LGraphTexture.prototype.onExecute=function(){var e=null;this.isOutputConnected(1)&& +(e=this.getInputData(0));!e&&this._drop_texture&&(e=this._drop_texture);!e&&this.properties.name&&(e=LGraphTexture.getTexture(this.properties.name));if(e){this._last_tex=e;!1===this.properties.filter?e.setParameter(gl.TEXTURE_MAG_FILTER,gl.NEAREST):e.setParameter(gl.TEXTURE_MAG_FILTER,gl.LINEAR);this.setOutputData(0,e);for(var d=1;d=this.size[1]))if(this._drop_texture&&e.webgl)e.drawImage(this._drop_texture,0,0,this.size[0],this.size[1]);else{if(this._last_preview_tex!=this._last_tex)if(e.webgl)this._canvas=this._last_tex;else{var d=LGraphTexture.generateLowResTexturePreview(this._last_tex);if(!d)return;this._last_preview_tex=this._last_tex; +this._canvas=cloneCanvas(d)}this._canvas&&(e.save(),e.webgl||(e.translate(0,this.size[1]),e.scale(1,-1)),e.drawImage(this._canvas,0,0,this.size[0],this.size[1]),e.restore())}};LGraphTexture.generateLowResTexturePreview=function(e){if(!e)return null;var d=LGraphTexture.image_preview_size,f=e;if(e.format==gl.DEPTH_COMPONENT)return null;if(e.width>d||e.height>d)f=this._preview_temp_tex,this._preview_temp_tex||(this._preview_temp_tex=f=new GL.Texture(d,d,{minFilter:gl.NEAREST})),e.copyTo(f);e=this._preview_canvas; +e||(this._preview_canvas=e=createCanvas(d,d));f&&f.toCanvas(e);return e};LGraphTexture.prototype.getResources=function(e){e[this.properties.name]=GL.Texture;return e};LGraphTexture.prototype.onGetInputs=function(){return[["in","Texture"]]};LGraphTexture.prototype.onGetOutputs=function(){return[["width","number"],["height","number"],["aspect","number"]]};LiteGraph.registerNodeType("texture/texture",LGraphTexture);var LGraphTexturePreview=function(){this.addInput("Texture","Texture");this.properties= +{flipY:!1};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=!1;LGraphTexturePreview.prototype.onDrawBackground=function(e){if(!this.flags.collapsed&&(e.webgl||LGraphTexturePreview.allow_preview)){var d=this.getInputData(0);if(d){var f=null,f=!d.handle&&e.webgl?d:LGraphTexture.generateLowResTexturePreview(d);e.save();this.properties.flipY&& +(e.translate(0,this.size[1]),e.scale(1,-1));e.drawImage(f,0,0,this.size[0],this.size[1]);e.restore()}}};LiteGraph.registerNodeType("texture/preview",LGraphTexturePreview);var LGraphTextureSave=function(){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 e=this.getInputData(0);e&&(this.properties.name&&(LGraphTexture.storeTexture? +LGraphTexture.storeTexture(this.properties.name,e):LGraphTexture.getTexturesContainer()[this.properties.name]=e),this.setOutputData(0,e))};LiteGraph.registerNodeType("texture/save",LGraphTextureSave);var LGraphTextureOperation=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: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(e){var d=this;return[{content:d.properties.show?"Hide Texture": +"Show Texture",callback:function(){d.properties.show=!d.properties.show}}]};LGraphTextureOperation.prototype.onDrawBackground=function(e){this.flags.collapsed||20>=this.size[1]||!this.properties.show||!this._tex||this._tex.gl!=e||(e.save(),e.drawImage(this._tex,0,0,this.size[0],this.size[1]),e.restore())};LGraphTextureOperation.prototype.onExecute=function(){var e=this.getInputData(0);if(this.isOutputConnected(0))if(this.properties.precision===LGraphTexture.PASS_THROUGH)this.setOutputData(0,e);else{var d= +this.getInputData(1);if(this.properties.uvcode||this.properties.pixelcode){var f=512,h=512;e?(f=e.width,h=e.height):d&&(f=d.width,h=d.height);this._tex=e||this._tex?LGraphTexture.getTargetTexture(e||this._tex,this._tex,this.properties.precision):new GL.Texture(f,h,{type:this.precision===LGraphTexture.LOW?gl.UNSIGNED_BYTE:gl.HIGH_PRECISION_FORMAT,format:gl.RGBA,filter:gl.LINEAR});var n="";this.properties.uvcode&&(n="uv = "+this.properties.uvcode,-1!=this.properties.uvcode.indexOf(";")&&(n=this.properties.uvcode)); +var l="";this.properties.pixelcode&&(l="result = "+this.properties.pixelcode,-1!=this.properties.pixelcode.indexOf(";")&&(l=this.properties.pixelcode));var a=this._shader;if(!a||this._shader_code!=n+"|"+l){try{this._shader=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,LGraphTextureOperation.pixel_shader,{UV_CODE:n,PIXEL_CODE:l}),this.boxcolor="#00FF00"}catch(b){console.log("Error compiling shader: ",b);this.boxcolor="#FF0000";return}this.boxcolor="#FF0000";this._shader_code=n+"|"+l;a=this._shader}if(a){this.boxcolor= +"green";var c=this.getInputData(2);null!=c?this.properties.value=c:c=parseFloat(this.properties.value);var g=this.graph.getTime();this._tex.drawTo(function(){gl.disable(gl.DEPTH_TEST);gl.disable(gl.CULL_FACE);gl.disable(gl.BLEND);e&&e.bind(0);d&&d.bind(1);var b=Mesh.getScreenQuad();a.uniforms({u_texture:0,u_textureB:1,value:c,texSize:[f,h],time:g}).draw(b)});this.setOutputData(0,this._tex)}else this.boxcolor="red"}}};LGraphTextureOperation.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"; +LiteGraph.registerNodeType("texture/operation",LGraphTextureOperation);var LGraphTextureShader=function(){this.addOutput("Texture","Texture");this.properties={code:"",width:512,height:512};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"};LGraphTextureShader.title="Shader";LGraphTextureShader.desc="Texture shader";LGraphTextureShader.widgets_info={code:{type:"code"},precision:{widget:"combo",values:LGraphTexture.MODE_VALUES}}; +LGraphTextureShader.prototype.onPropertyChanged=function(e,d){if("code"==e){var f=this.getShader();if(f){var h=f.uniformInfo;if(this.inputs)for(var n={},l=0;lf;++f)e[f]=Math.random();LGraphTextureAverage._shader.uniforms({u_samples_a:e.subarray(0,16),u_samples_b:e.subarray(16,32)})}e=this._temp_texture;f=this.properties.low_precision?gl.UNSIGNED_BYTE:d.type;e&&e.type== -f||(this._temp_texture=new GL.Texture(1,1,{type:f,format:gl.RGBA,filter:gl.NEAREST}));var k=LGraphTextureAverage._shader;this._temp_texture.drawTo(function(){d.toViewport(k,{u_texture:0})});this.setOutputData(0,this._temp_texture)}};LGraphTextureAverage.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\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] ) );\n\t\t\t\t\t\tcolor += texture2D(u_texture, vec2( 1.0 - u_samples_a[i][j], u_samples_b[i][j] ) );\n\t\t\t\t\t}\n\t\t\t gl_FragColor = color * 0.03125;\n\t\t\t}\n\t\t\t"; -LiteGraph.registerNodeType("texture/average",LGraphTextureAverage);var LGraphImageToTexture=function(){this.addInput("Image","image");this.addOutput("","Texture");this.properties={}};LGraphImageToTexture.title="Image to Texture";LGraphImageToTexture.desc="Uploads an image to the GPU";LGraphImageToTexture.prototype.onExecute=function(){var d=this.getInputData(0);if(d){var e=d.videoWidth||d.width,f=d.videoHeight||d.height;if(d.gltexture)this.setOutputData(0,d.gltexture);else{var k=this._temp_texture; -k&&k.width==e&&k.height==f||(this._temp_texture=new GL.Texture(e,f,{format:gl.RGBA,filter:gl.LINEAR}));try{this._temp_texture.uploadImage(d)}catch(h){console.error("image comes from an unsafe location, cannot be uploaded to webgl");return}this.setOutputData(0,this._temp_texture)}}};LiteGraph.registerNodeType("texture/imageToTexture",LGraphImageToTexture);var LGraphTextureLUT=function(){this.addInput("Texture","Texture");this.addInput("LUT","Texture");this.addInput("Intensity","number");this.addOutput("", -"Texture");this.properties={intensity:1,precision:LGraphTexture.DEFAULT,texture:null};LGraphTextureLUT._shader||(LGraphTextureLUT._shader=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,LGraphTextureLUT.pixel_shader))};LGraphTextureLUT.widgets_info={precision:{widget:"combo",values:LGraphTexture.MODE_VALUES}};LGraphTextureLUT.title="LUT";LGraphTextureLUT.desc="Apply LUT to Texture";LGraphTextureLUT.widgets_info={texture:{widget:"texture"}};LGraphTextureLUT.prototype.onExecute=function(){if(this.isOutputConnected(0)){var d= -this.getInputData(0);if(this.properties.precision===LGraphTexture.PASS_THROUGH)this.setOutputData(0,d);else if(d){var e=this.getInputData(1);e||(e=LGraphTexture.getTexture(this.properties.texture));if(e){e.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=LGraphTexture.getTargetTexture(d,this._tex,this.properties.precision);this._tex.drawTo(function(){e.bind(1);d.toViewport(LGraphTextureLUT._shader,{u_texture:0,u_textureB:1,u_amount:f})});this.setOutputData(0,this._tex)}else this.setOutputData(0,d)}}};LGraphTextureLUT.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"; +"Texture");this.addOutput("","Texture");this.properties={size:0,generate_mipmaps:!1,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 e=this.getInputData(0);if((e||this._temp_texture)&&this.isOutputConnected(0)){if(e){var d=e.width,f=e.height; +0!=this.properties.size&&(f=d=this.properties.size);var h=this._temp_texture,n=e.type;this.properties.precision===LGraphTexture.LOW?n=gl.UNSIGNED_BYTE:this.properties.precision===LGraphTexture.HIGH&&(n=gl.HIGH_PRECISION_FORMAT);h&&h.width==d&&h.height==f&&h.type==n||(h=gl.LINEAR,this.properties.generate_mipmaps&&isPowerOfTwo(d)&&isPowerOfTwo(f)&&(h=gl.LINEAR_MIPMAP_LINEAR),this._temp_texture=new GL.Texture(d,f,{type:n,format:gl.RGBA,minFilter:h,magFilter:gl.LINEAR}));e.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)}};LiteGraph.registerNodeType("texture/copy",LGraphTextureCopy);var LGraphTextureDownsample=function(){this.addInput("Texture","Texture");this.addOutput("","Texture");this.properties={iterations:1,generate_mipmaps:!1,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 e=this.getInputData(0);if((e||this._temp_texture)&&this.isOutputConnected(0)&&e&&e.texture_type===GL.TEXTURE_2D){var d=LGraphTextureDownsample._shader;d||(LGraphTextureDownsample._shader=d=new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER,LGraphTextureDownsample.pixel_shader)); +var f=e.width|0,h=e.height|0,n=e.type;this.properties.precision===LGraphTexture.LOW?n=gl.UNSIGNED_BYTE:this.properties.precision===LGraphTexture.HIGH&&(n=gl.HIGH_PRECISION_FORMAT);var l=this.properties.iterations||1,a=e,b=null,c=[],e={type:n,format:e.format},n=vec2.create(),g={u_offset:n};this._texture&&GL.Texture.releaseTemporary(this._texture);for(var k=0;k>1||0;h=h>>1||0;b=GL.Texture.getTemporary(f,h,e);c.push(b);a.setParameter(GL.TEXTURE_MAG_FILTER,GL.NEAREST);a.copyTo(b, +d,g);if(1==f&&1==h)break;a=b}this._texture=c.pop();for(k=0;kf;++f)d[f]=Math.random();LGraphTextureAverage._shader.uniforms({u_samples_a:d.subarray(0,16),u_samples_b:d.subarray(16,32)})}d=this._temp_texture;f=this.properties.low_precision?gl.UNSIGNED_BYTE:e.type;d&&d.type==f||(this._temp_texture=new GL.Texture(1, +1,{type:f,format:gl.RGBA,filter:gl.NEAREST}));var h=LGraphTextureAverage._shader,n=this._uniforms;n.u_mipmap_offset=this.properties.mipmap_offset;this._temp_texture.drawTo(function(){e.toViewport(h,n)});this.setOutputData(0,this._temp_texture)}};LGraphTextureAverage.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"; +LiteGraph.registerNodeType("texture/average",LGraphTextureAverage);var LGraphImageToTexture=function(){this.addInput("Image","image");this.addOutput("","Texture");this.properties={}};LGraphImageToTexture.title="Image to Texture";LGraphImageToTexture.desc="Uploads an image to the GPU";LGraphImageToTexture.prototype.onExecute=function(){var e=this.getInputData(0);if(e){var d=e.videoWidth||e.width,f=e.videoHeight||e.height;if(e.gltexture)this.setOutputData(0,e.gltexture);else{var h=this._temp_texture; +h&&h.width==d&&h.height==f||(this._temp_texture=new GL.Texture(d,f,{format:gl.RGBA,filter:gl.LINEAR}));try{this._temp_texture.uploadImage(e)}catch(n){console.error("image comes from an unsafe location, cannot be uploaded to webgl");return}this.setOutputData(0,this._temp_texture)}}};LiteGraph.registerNodeType("texture/imageToTexture",LGraphImageToTexture);var LGraphTextureLUT=function(){this.addInput("Texture","Texture");this.addInput("LUT","Texture");this.addInput("Intensity","number");this.addOutput("", +"Texture");this.properties={intensity:1,precision:LGraphTexture.DEFAULT,texture:null};LGraphTextureLUT._shader||(LGraphTextureLUT._shader=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,LGraphTextureLUT.pixel_shader))};LGraphTextureLUT.widgets_info={precision:{widget:"combo",values:LGraphTexture.MODE_VALUES}};LGraphTextureLUT.title="LUT";LGraphTextureLUT.desc="Apply LUT to Texture";LGraphTextureLUT.widgets_info={texture:{widget:"texture"}};LGraphTextureLUT.prototype.onExecute=function(){if(this.isOutputConnected(0)){var e= +this.getInputData(0);if(this.properties.precision===LGraphTexture.PASS_THROUGH)this.setOutputData(0,e);else if(e){var d=this.getInputData(1);d||(d=LGraphTexture.getTexture(this.properties.texture));if(d){d.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=LGraphTexture.getTargetTexture(e,this._tex,this.properties.precision);this._tex.drawTo(function(){d.bind(1);e.toViewport(LGraphTextureLUT._shader,{u_texture:0,u_textureB:1,u_amount:f})});this.setOutputData(0,this._tex)}else this.setOutputData(0,e)}}};LGraphTextureLUT.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"; LiteGraph.registerNodeType("texture/LUT",LGraphTextureLUT);var LGraphTextureChannels=function(){this.addInput("Texture","Texture");this.addOutput("R","Texture");this.addOutput("G","Texture");this.addOutput("B","Texture");this.addOutput("A","Texture");this.properties={};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 d=this.getInputData(0);if(d){this._channels||(this._channels=Array(4));for(var e=0,f=0;4>f;f++)this.isOutputConnected(f)?(this._channels[f]&&this._channels[f].width==d.width&&this._channels[f].height==d.height&&this._channels[f].type==d.type||(this._channels[f]=new GL.Texture(d.width,d.height,{type:d.type,format:gl.RGBA,filter:gl.LINEAR})),e++):this._channels[f]=null;if(e){gl.disable(gl.BLEND);gl.disable(gl.DEPTH_TEST);for(var k=Mesh.getScreenQuad(), -h=LGraphTextureChannels._shader,a=[[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]],f=0;4>f;f++)this._channels[f]&&(this._channels[f].drawTo(function(){d.bind(0);h.uniforms({u_texture:0,u_mask:a[f]}).draw(k)}),this.setOutputData(f,this._channels[f]))}}};LGraphTextureChannels.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"; +LGraphTextureChannels.prototype.onExecute=function(){var e=this.getInputData(0);if(e){this._channels||(this._channels=Array(4));for(var d=0,f=0;4>f;f++)this.isOutputConnected(f)?(this._channels[f]&&this._channels[f].width==e.width&&this._channels[f].height==e.height&&this._channels[f].type==e.type||(this._channels[f]=new GL.Texture(e.width,e.height,{type:e.type,format:gl.RGBA,filter:gl.LINEAR})),d++):this._channels[f]=null;if(d){gl.disable(gl.BLEND);gl.disable(gl.DEPTH_TEST);for(var h=Mesh.getScreenQuad(), +n=LGraphTextureChannels._shader,l=[[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]],f=0;4>f;f++)this._channels[f]&&(this._channels[f].drawTo(function(){e.bind(0);n.uniforms({u_texture:0,u_mask:l[f]}).draw(h)}),this.setOutputData(f,this._channels[f]))}}};LGraphTextureChannels.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"; LiteGraph.registerNodeType("texture/textureChannels",LGraphTextureChannels);var LGraphChannelsTexture=function(){this.addInput("R","Texture");this.addInput("G","Texture");this.addInput("B","Texture");this.addInput("A","Texture");this.addOutput("Texture","Texture");this.properties={};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 d=[this.getInputData(0),this.getInputData(1),this.getInputData(2),this.getInputData(3)];if(d[0]&&d[1]&&d[2]&&d[3]){gl.disable(gl.BLEND);gl.disable(gl.DEPTH_TEST);var e=Mesh.getScreenQuad(),f=LGraphChannelsTexture._shader;this._tex=LGraphTexture.getTargetTexture(d[0],this._tex);this._tex.drawTo(function(){d[0].bind(0);d[1].bind(1);d[2].bind(2);d[3].bind(3);f.uniforms({u_textureR:0,u_textureG:1,u_textureB:2,u_textureA:3}).draw(e)}); +"Split texture channels";LGraphChannelsTexture.prototype.onExecute=function(){var e=[this.getInputData(0),this.getInputData(1),this.getInputData(2),this.getInputData(3)];if(e[0]&&e[1]&&e[2]&&e[3]){gl.disable(gl.BLEND);gl.disable(gl.DEPTH_TEST);var d=Mesh.getScreenQuad(),f=LGraphChannelsTexture._shader;this._tex=LGraphTexture.getTargetTexture(e[0],this._tex);this._tex.drawTo(function(){e[0].bind(0);e[1].bind(1);e[2].bind(2);e[3].bind(3);f.uniforms({u_textureR:0,u_textureG:1,u_textureB:2,u_textureA:3}).draw(d)}); this.setOutputData(0,this._tex)}};LGraphChannelsTexture.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"; LiteGraph.registerNodeType("texture/channelsTexture",LGraphChannelsTexture);var LGraphTextureGradient=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};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 d=GL.Mesh.getScreenQuad(),e=LGraphTextureGradient._shader,f=this.getInputData(0);f||(f=this.properties.A);var k=this.getInputData(1);k||(k=this.properties.B);for(var h=2;h< -this.inputs.length;h++){var a=this.inputs[h],b=this.getInputData(h);void 0!==b&&(this.properties[a.name]=b)}var c=this._uniforms;this._uniforms.u_angle=this.properties.angle*DEG2RAD;this._uniforms.u_scale=this.properties.scale;vec3.copy(c.u_colorA,f);vec3.copy(c.u_colorB,k);f=parseInt(this.properties.texture_size);this._tex&&this._tex.width==f||(this._tex=new GL.Texture(f,f,{format:gl.RGB,filter:gl.LINEAR}));this._tex.drawTo(function(){e.uniforms(c).draw(d)});this.setOutputData(0,this._tex)};LGraphTextureGradient.prototype.onGetInputs= +"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 e=GL.Mesh.getScreenQuad(),d=LGraphTextureGradient._shader,f=this.getInputData(0);f||(f=this.properties.A);var h=this.getInputData(1);h||(h=this.properties.B);for(var n=2;n< +this.inputs.length;n++){var l=this.inputs[n],a=this.getInputData(n);void 0!==a&&(this.properties[l.name]=a)}var b=this._uniforms;this._uniforms.u_angle=this.properties.angle*DEG2RAD;this._uniforms.u_scale=this.properties.scale;vec3.copy(b.u_colorA,f);vec3.copy(b.u_colorB,h);f=parseInt(this.properties.texture_size);this._tex&&this._tex.width==f||(this._tex=new GL.Texture(f,f,{format:gl.RGB,filter:gl.LINEAR}));this._tex.drawTo(function(){d.uniforms(b).draw(e)});this.setOutputData(0,this._tex)};LGraphTextureGradient.prototype.onGetInputs= function(){return[["angle","number"],["scale","number"]]};LGraphTextureGradient.pixel_shader="precision highp float;\n\t\t\tprecision highp float;\n\t\t\tvarying vec2 v_coord;\n\t\t\tuniform float u_angle;\n\t\t\tuniform float u_scale;\n\t\t\tuniform vec3 u_colorA;\n\t\t\tuniform vec3 u_colorB;\n\t\t\t\n\t\t\tvec2 rotate(vec2 v, float angle)\n\t\t\t{\n\t\t\t\tvec2 result;\n\t\t\t\tfloat _cos = cos(angle);\n\t\t\t\tfloat _sin = sin(angle);\n\t\t\t\tresult.x = v.x * _cos - v.y * _sin;\n\t\t\t\tresult.y = v.x * _sin + v.y * _cos;\n\t\t\t\treturn result;\n\t\t\t}\n\t\t\tvoid main() {\n\t\t\t\tfloat f = (rotate(u_scale * (v_coord - vec2(0.5)), u_angle) + vec2(0.5)).x;\n\t\t\t\tvec3 color = mix(u_colorA,u_colorB,clamp(f,0.0,1.0));\n\t\t\t gl_FragColor = vec4(color,1.0);\n\t\t\t}\n\t\t\t"; LiteGraph.registerNodeType("texture/gradient",LGraphTextureGradient);var LGraphTextureMix=function(){this.addInput("A","Texture");this.addInput("B","Texture");this.addInput("Mixer","Texture");this.addOutput("Texture","Texture");this.properties={precision:LGraphTexture.DEFAULT};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 d=this.getInputData(0);if(this.isOutputConnected(0))if(this.properties.precision===LGraphTexture.PASS_THROUGH)this.setOutputData(0,d);else{var e=this.getInputData(1),f=this.getInputData(2);if(d&&e&&f){this._tex=LGraphTexture.getTargetTexture(d,this._tex,this.properties.precision);gl.disable(gl.BLEND);gl.disable(gl.DEPTH_TEST);var k=Mesh.getScreenQuad(),h=LGraphTextureMix._shader;this._tex.drawTo(function(){d.bind(0); -e.bind(1);f.bind(2);h.uniforms({u_textureA:0,u_textureB:1,u_textureMix:2}).draw(k)});this.setOutputData(0,this._tex)}}};LGraphTextureMix.pixel_shader="precision highp float;\n\t\t\tprecision highp float;\n\t\t\tvarying vec2 v_coord;\n\t\t\tuniform sampler2D u_textureA;\n\t\t\tuniform sampler2D u_textureB;\n\t\t\tuniform sampler2D u_textureMix;\n\t\t\t\n\t\t\tvoid main() {\n\t\t\t gl_FragColor = mix( texture2D(u_textureA, v_coord), texture2D(u_textureB, v_coord), texture2D(u_textureMix, v_coord) );\n\t\t\t}\n\t\t\t"; +{precision:{widget:"combo",values:LGraphTexture.MODE_VALUES}};LGraphTextureMix.prototype.onExecute=function(){var e=this.getInputData(0);if(this.isOutputConnected(0))if(this.properties.precision===LGraphTexture.PASS_THROUGH)this.setOutputData(0,e);else{var d=this.getInputData(1),f=this.getInputData(2);if(e&&d&&f){this._tex=LGraphTexture.getTargetTexture(e,this._tex,this.properties.precision);gl.disable(gl.BLEND);gl.disable(gl.DEPTH_TEST);var h=Mesh.getScreenQuad(),n=LGraphTextureMix._shader;this._tex.drawTo(function(){e.bind(0); +d.bind(1);f.bind(2);n.uniforms({u_textureA:0,u_textureB:1,u_textureMix:2}).draw(h)});this.setOutputData(0,this._tex)}}};LGraphTextureMix.pixel_shader="precision highp float;\n\t\t\tprecision highp float;\n\t\t\tvarying vec2 v_coord;\n\t\t\tuniform sampler2D u_textureA;\n\t\t\tuniform sampler2D u_textureB;\n\t\t\tuniform sampler2D u_textureMix;\n\t\t\t\n\t\t\tvoid main() {\n\t\t\t gl_FragColor = mix( texture2D(u_textureA, v_coord), texture2D(u_textureB, v_coord), texture2D(u_textureMix, v_coord) );\n\t\t\t}\n\t\t\t"; LiteGraph.registerNodeType("texture/mix",LGraphTextureMix);var LGraphTextureEdges=function(){this.addInput("Tex.","Texture");this.addOutput("Edges","Texture");this.properties={invert:!0,factor:1,precision:LGraphTexture.DEFAULT};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)){var d=this.getInputData(0);if(this.properties.precision===LGraphTexture.PASS_THROUGH)this.setOutputData(0,d);else if(d){this._tex=LGraphTexture.getTargetTexture(d,this._tex,this.properties.precision);gl.disable(gl.BLEND);gl.disable(gl.DEPTH_TEST);var e=Mesh.getScreenQuad(),f=LGraphTextureEdges._shader,k=this.properties.invert,h=this.properties.factor;this._tex.drawTo(function(){d.bind(0);f.uniforms({u_texture:0,u_isize:[1/ -d.width,1/d.height],u_factor:h,u_invert:k?1:0}).draw(e)});this.setOutputData(0,this._tex)}}};LGraphTextureEdges.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_isize;\n\t\t\tuniform int u_invert;\n\t\t\tuniform float u_factor;\n\t\t\t\n\t\t\tvoid main() {\n\t\t\t\tvec4 center = texture2D(u_texture, v_coord);\n\t\t\t\tvec4 up = texture2D(u_texture, v_coord + u_isize * vec2(0.0,1.0) );\n\t\t\t\tvec4 down = texture2D(u_texture, v_coord + u_isize * vec2(0.0,-1.0) );\n\t\t\t\tvec4 left = texture2D(u_texture, v_coord + u_isize * vec2(1.0,0.0) );\n\t\t\t\tvec4 right = texture2D(u_texture, v_coord + u_isize * vec2(-1.0,0.0) );\n\t\t\t\tvec4 diff = abs(center - up) + abs(center - down) + abs(center - left) + abs(center - right);\n\t\t\t\tdiff *= u_factor;\n\t\t\t\tif(u_invert == 1)\n\t\t\t\t\tdiff.xyz = vec3(1.0) - diff.xyz;\n\t\t\t gl_FragColor = vec4( diff.xyz, center.a );\n\t\t\t}\n\t\t\t"; -LiteGraph.registerNodeType("texture/edges",LGraphTextureEdges);var LGraphTextureDepthRange=function(){this.addInput("Texture","Texture");this.addInput("Distance","number");this.addInput("Range","number");this.addOutput("Texture","Texture");this.properties={distance:100,range:50,high_precision:!1}};LGraphTextureDepthRange.title="Depth Range";LGraphTextureDepthRange.desc="Generates a texture with a depth range";LGraphTextureDepthRange.prototype.onExecute=function(){if(this.isOutputConnected(0)){var d= -this.getInputData(0);if(d){var e=gl.UNSIGNED_BYTE;this.properties.high_precision&&(e=gl.half_float_ext?gl.HALF_FLOAT_OES:gl.FLOAT);this._temp_texture&&this._temp_texture.type==e&&this._temp_texture.width==d.width&&this._temp_texture.height==d.height||(this._temp_texture=new GL.Texture(d.width,d.height,{type:e,format:gl.RGBA,filter:gl.LINEAR}));var f=this.properties.distance;this.isInputConnected(1)&&(f=this.getInputData(1),this.properties.distance=f);var k=this.properties.range;this.isInputConnected(2)&& -(k=this.getInputData(2),this.properties.range=k);gl.disable(gl.BLEND);gl.disable(gl.DEPTH_TEST);var h=Mesh.getScreenQuad();LGraphTextureDepthRange._shader||(LGraphTextureDepthRange._shader=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,LGraphTextureDepthRange.pixel_shader));var a=LGraphTextureDepthRange._shader,b=[LS.Renderer._current_camera.near,LS.Renderer._current_camera.far];this._temp_texture.drawTo(function(){d.bind(0);a.uniforms({u_texture:0,u_distance:f,u_range:k,u_camera_planes:b}).draw(h)}); +LGraphTextureEdges.prototype.onExecute=function(){if(this.isOutputConnected(0)){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);gl.disable(gl.BLEND);gl.disable(gl.DEPTH_TEST);var d=Mesh.getScreenQuad(),f=LGraphTextureEdges._shader,h=this.properties.invert,n=this.properties.factor;this._tex.drawTo(function(){e.bind(0);f.uniforms({u_texture:0,u_isize:[1/ +e.width,1/e.height],u_factor:n,u_invert:h?1:0}).draw(d)});this.setOutputData(0,this._tex)}}};LGraphTextureEdges.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_isize;\n\t\t\tuniform int u_invert;\n\t\t\tuniform float u_factor;\n\t\t\t\n\t\t\tvoid main() {\n\t\t\t\tvec4 center = texture2D(u_texture, v_coord);\n\t\t\t\tvec4 up = texture2D(u_texture, v_coord + u_isize * vec2(0.0,1.0) );\n\t\t\t\tvec4 down = texture2D(u_texture, v_coord + u_isize * vec2(0.0,-1.0) );\n\t\t\t\tvec4 left = texture2D(u_texture, v_coord + u_isize * vec2(1.0,0.0) );\n\t\t\t\tvec4 right = texture2D(u_texture, v_coord + u_isize * vec2(-1.0,0.0) );\n\t\t\t\tvec4 diff = abs(center - up) + abs(center - down) + abs(center - left) + abs(center - right);\n\t\t\t\tdiff *= u_factor;\n\t\t\t\tif(u_invert == 1)\n\t\t\t\t\tdiff.xyz = vec3(1.0) - diff.xyz;\n\t\t\t gl_FragColor = vec4( diff.xyz, center.a );\n\t\t\t}\n\t\t\t"; +LiteGraph.registerNodeType("texture/edges",LGraphTextureEdges);var LGraphTextureDepthRange=function(){this.addInput("Texture","Texture");this.addInput("Distance","number");this.addInput("Range","number");this.addOutput("Texture","Texture");this.properties={distance:100,range:50,high_precision:!1}};LGraphTextureDepthRange.title="Depth Range";LGraphTextureDepthRange.desc="Generates a texture with a depth range";LGraphTextureDepthRange.prototype.onExecute=function(){if(this.isOutputConnected(0)){var e= +this.getInputData(0);if(e){var d=gl.UNSIGNED_BYTE;this.properties.high_precision&&(d=gl.half_float_ext?gl.HALF_FLOAT_OES:gl.FLOAT);this._temp_texture&&this._temp_texture.type==d&&this._temp_texture.width==e.width&&this._temp_texture.height==e.height||(this._temp_texture=new GL.Texture(e.width,e.height,{type:d,format:gl.RGBA,filter:gl.LINEAR}));var f=this.properties.distance;this.isInputConnected(1)&&(f=this.getInputData(1),this.properties.distance=f);var h=this.properties.range;this.isInputConnected(2)&& +(h=this.getInputData(2),this.properties.range=h);gl.disable(gl.BLEND);gl.disable(gl.DEPTH_TEST);var n=Mesh.getScreenQuad();LGraphTextureDepthRange._shader||(LGraphTextureDepthRange._shader=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,LGraphTextureDepthRange.pixel_shader));var l=LGraphTextureDepthRange._shader,a=[LS.Renderer._current_camera.near,LS.Renderer._current_camera.far];this._temp_texture.drawTo(function(){e.bind(0);l.uniforms({u_texture:0,u_distance:f,u_range:h,u_camera_planes:a}).draw(n)}); this.setOutputData(0,this._temp_texture)}}};LGraphTextureDepthRange.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 n = u_camera_planes.x;\n\t\t\t\tfloat f = u_camera_planes.y;\n\t\t\t\treturn (2.0 * n) / (f + n - texture2D(u_texture, v_coord).x * (f - n));\n\t\t\t}\n\t\t\t\n\t\t\tvoid main() {\n\t\t\t\tfloat diff = abs(LinearDepth() * u_camera_planes.y - u_distance);\n\t\t\t\tfloat dof = 1.0;\n\t\t\t\tif(diff <= u_range)\n\t\t\t\t\tdof = diff / u_range;\n\t\t\t gl_FragColor = vec4(dof);\n\t\t\t}\n\t\t\t"; -LiteGraph.registerNodeType("texture/depth_range",LGraphTextureDepthRange);var LGraphTextureBlur=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]}};LGraphTextureBlur.title="Blur";LGraphTextureBlur.desc="Blur a texture";LGraphTextureBlur.max_iterations=20;LGraphTextureBlur.prototype.onExecute=function(){var d=this.getInputData(0); -if(d&&this.isOutputConnected(0)){var e=this._temp_texture;e&&e.width==d.width&&e.height==d.height&&e.type==d.type||(this._temp_texture=new GL.Texture(d.width,d.height,{type:d.type,format:gl.RGBA,filter:gl.LINEAR}),this._final_texture=new GL.Texture(d.width,d.height,{type:d.type,format:gl.RGBA,filter:gl.LINEAR}));e=this.properties.iterations;this.isInputConnected(1)&&(e=this.getInputData(1),this.properties.iterations=e);e=Math.min(Math.floor(e),LGraphTextureBlur.max_iterations);if(0==e)this.setOutputData(0, -d);else{var f=this.properties.intensity;this.isInputConnected(2)&&(f=this.getInputData(2),this.properties.intensity=f);var k=LiteGraph.camera_aspect;k||void 0===window.gl||(k=gl.canvas.height/gl.canvas.width);k||(k=1);for(var k=this.properties.preserve_aspect?k:1,h=this.properties.scale||[1,1],a=0;a=this.size[1]||!this._video||(d.save(),d.webgl?this._temp_texture&&d.drawImage(this._temp_texture,0,0,this.size[0],this.size[1]):(d.translate(0,this.size[1]),d.scale(1,-1),d.drawImage(this._video,0,0,this.size[0],this.size[1])),d.restore())};LGraphTextureWebcam.prototype.onExecute=function(){null!=this._webcam_stream||this._waiting_confirmation||this.openStream();if(this._video&& -this._video.videoWidth){var d=this._video.videoWidth,e=this._video.videoHeight,f=this._temp_texture;f&&f.width==d&&f.height==e||(this._temp_texture=new GL.Texture(d,e,{format:gl.RGB,filter:gl.LINEAR}));this._temp_texture.uploadImage(this._video);this.properties.texture_name&&(LGraphTexture.getTexturesContainer()[this.properties.texture_name]=this._temp_texture);this.setOutputData(0,this._temp_texture)}};LiteGraph.registerNodeType("texture/webcam",LGraphTextureWebcam);var LGraphTextureMatte=function(){this.addInput("in", +LiteGraph.registerNodeType("texture/kuwahara",LGraphTextureKuwaharaFilter);var LGraphTextureWebcam=function(){this.addOutput("Webcam","Texture");this.properties={texture_name:""}};LGraphTextureWebcam.title="Webcam";LGraphTextureWebcam.desc="Webcam texture";LGraphTextureWebcam.prototype.openStream=function(){function e(e){console.log("Webcam rejected",e);d._webcam_stream=!1;d.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 d=this;navigator.getUserMedia({video:!0},this.streamReady.bind(this),e)}};LGraphTextureWebcam.prototype.streamReady=function(e){this._webcam_stream=e;var d=this._video;d||(d=document.createElement("video"),d.autoplay=!0,d.src=window.URL.createObjectURL(e),this._video=d,d.onloadedmetadata=function(d){console.log(d)})};LGraphTextureWebcam.prototype.onRemoved=function(){this._webcam_stream&&(this._webcam_stream.stop(), +this._video=this._webcam_stream=null)};LGraphTextureWebcam.prototype.onDrawBackground=function(e){this.flags.collapsed||20>=this.size[1]||!this._video||(e.save(),e.webgl?this._temp_texture&&e.drawImage(this._temp_texture,0,0,this.size[0],this.size[1]):(e.translate(0,this.size[1]),e.scale(1,-1),e.drawImage(this._video,0,0,this.size[0],this.size[1])),e.restore())};LGraphTextureWebcam.prototype.onExecute=function(){null!=this._webcam_stream||this._waiting_confirmation||this.openStream();if(this._video&& +this._video.videoWidth){var e=this._video.videoWidth,d=this._video.videoHeight,f=this._temp_texture;f&&f.width==e&&f.height==d||(this._temp_texture=new GL.Texture(e,d,{format:gl.RGB,filter:gl.LINEAR}));this._temp_texture.uploadImage(this._video);this.properties.texture_name&&(LGraphTexture.getTexturesContainer()[this.properties.texture_name]=this._temp_texture);this.setOutputData(0,this._temp_texture)}};LiteGraph.registerNodeType("texture/webcam",LGraphTextureWebcam);var LGraphTextureMatte=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:LGraphTexture.DEFAULT};LGraphTextureMatte._shader||(LGraphTextureMatte._shader=new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER,LGraphTextureMatte.pixel_shader))};LGraphTextureMatte.title="Matte";LGraphTextureMatte.desc="Extracts background";LGraphTextureMatte.widgets_info={key_color:{widget:"color"},precision:{widget:"combo",values:LGraphTexture.MODE_VALUES}};LGraphTextureMatte.prototype.onExecute= -function(){if(this.isOutputConnected(0)){var d=this.getInputData(0);if(this.properties.precision===LGraphTexture.PASS_THROUGH)this.setOutputData(0,d);else if(d){this._tex=LGraphTexture.getTargetTexture(d,this._tex,this.properties.precision);gl.disable(gl.BLEND);gl.disable(gl.DEPTH_TEST);this._uniforms||(this._uniforms={u_texture:0,u_key_color:this.properties.key_color,u_threshold:1,u_slope:1});var e=this._uniforms,f=Mesh.getScreenQuad(),k=LGraphTextureMatte._shader;e.u_key_color=this.properties.key_color; -e.u_threshold=this.properties.threshold;e.u_slope=this.properties.slope;this._tex.drawTo(function(){d.bind(0);k.uniforms(e).draw(f)});this.setOutputData(0,this._tex)}}};LGraphTextureMatte.pixel_shader="precision highp float;\n\t\t\tvarying vec2 v_coord;\n\t\t\tuniform sampler2D u_texture;\n\t\t\tuniform vec3 u_key_color;\n\t\t\tuniform float u_threshold;\n\t\t\tuniform float u_slope;\n\t\t\t\n\t\t\tvoid main() {\n\t\t\t\tvec3 color = texture2D( u_texture, v_coord ).xyz;\n\t\t\t\tfloat diff = length( normalize(color) - normalize(u_key_color) );\n\t\t\t\tfloat edge = u_threshold * (1.0 - u_slope);\n\t\t\t\tfloat alpha = smoothstep( edge, u_threshold, diff);\n\t\t\t\tgl_FragColor = vec4( color, alpha );\n\t\t\t}"; -LiteGraph.registerNodeType("texture/matte",LGraphTextureMatte);var LGraphCubemap=function(){this.addOutput("Cubemap","Cubemap");this.properties={name:""};this.size=[LGraphTexture.image_preview_size,LGraphTexture.image_preview_size]};LGraphCubemap.prototype.onDropFile=function(d,e,f){d?(this._drop_texture="string"==typeof d?GL.Texture.fromURL(d):GL.Texture.fromDDSInMemory(d),this.properties.name=e):(this._drop_texture=null,this.properties.name="")};LGraphCubemap.prototype.onExecute=function(){if(this._drop_texture)this.setOutputData(0, -this._drop_texture);else if(this.properties.name){var d=LGraphTexture.getTexture(this.properties.name);d&&(this._last_tex=d,this.setOutputData(0,d))}};LGraphCubemap.prototype.onDrawBackground=function(d){this.flags.collapsed||20>=this.size[1]||d.webgl&&(gl.meshes.cube||(gl.meshes.cube=GL.Mesh.cube({size:1})))};LiteGraph.registerNodeType("texture/cubemap",LGraphCubemap)} +function(){if(this.isOutputConnected(0)){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);gl.disable(gl.BLEND);gl.disable(gl.DEPTH_TEST);this._uniforms||(this._uniforms={u_texture:0,u_key_color:this.properties.key_color,u_threshold:1,u_slope:1});var d=this._uniforms,f=Mesh.getScreenQuad(),h=LGraphTextureMatte._shader;d.u_key_color=this.properties.key_color; +d.u_threshold=this.properties.threshold;d.u_slope=this.properties.slope;this._tex.drawTo(function(){e.bind(0);h.uniforms(d).draw(f)});this.setOutputData(0,this._tex)}}};LGraphTextureMatte.pixel_shader="precision highp float;\n\t\t\tvarying vec2 v_coord;\n\t\t\tuniform sampler2D u_texture;\n\t\t\tuniform vec3 u_key_color;\n\t\t\tuniform float u_threshold;\n\t\t\tuniform float u_slope;\n\t\t\t\n\t\t\tvoid main() {\n\t\t\t\tvec3 color = texture2D( u_texture, v_coord ).xyz;\n\t\t\t\tfloat diff = length( normalize(color) - normalize(u_key_color) );\n\t\t\t\tfloat edge = u_threshold * (1.0 - u_slope);\n\t\t\t\tfloat alpha = smoothstep( edge, u_threshold, diff);\n\t\t\t\tgl_FragColor = vec4( color, alpha );\n\t\t\t}"; +LiteGraph.registerNodeType("texture/matte",LGraphTextureMatte);var LGraphCubemap=function(){this.addOutput("Cubemap","Cubemap");this.properties={name:""};this.size=[LGraphTexture.image_preview_size,LGraphTexture.image_preview_size]};LGraphCubemap.title="Cubemap";LGraphCubemap.prototype.onDropFile=function(e,d,f){e?(this._drop_texture="string"==typeof e?GL.Texture.fromURL(e):GL.Texture.fromDDSInMemory(e),this.properties.name=d):(this._drop_texture=null,this.properties.name="")};LGraphCubemap.prototype.onExecute= +function(){if(this._drop_texture)this.setOutputData(0,this._drop_texture);else if(this.properties.name){var e=LGraphTexture.getTexture(this.properties.name);e&&(this._last_tex=e,this.setOutputData(0,e))}};LGraphCubemap.prototype.onDrawBackground=function(e){this.flags.collapsed||20>=this.size[1]||e.webgl&&(gl.meshes.cube||(gl.meshes.cube=GL.Mesh.cube({size:1})))};LiteGraph.registerNodeType("texture/cubemap",LGraphCubemap)} if("undefined"!=typeof LiteGraph){var LGraphFXLens=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};LGraphFXLens._shader||(LGraphFXLens._shader=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,LGraphFXLens.pixel_shader))};LGraphFXLens.title="Lens";LGraphFXLens.desc="Camera Lens distortion"; -LGraphFXLens.widgets_info={precision:{widget:"combo",values:LGraphTexture.MODE_VALUES}};LGraphFXLens.prototype.onExecute=function(){var d=this.getInputData(0);if(this.properties.precision===LGraphTexture.PASS_THROUGH)this.setOutputData(0,d);else if(d){this._tex=LGraphTexture.getTargetTexture(d,this._tex,this.properties.precision);var e=this.properties.aberration;this.isInputConnected(1)&&(e=this.getInputData(1),this.properties.aberration=e);var f=this.properties.distortion;this.isInputConnected(2)&& -(f=this.getInputData(2),this.properties.distortion=f);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 h=Mesh.getScreenQuad(),a=LGraphFXLens._shader;this._tex.drawTo(function(){d.bind(0);a.uniforms({u_texture:0,u_aberration:e,u_distortion:f,u_blur:k}).draw(h)});this.setOutputData(0,this._tex)}};LGraphFXLens.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"; +LGraphFXLens.widgets_info={precision:{widget:"combo",values:LGraphTexture.MODE_VALUES}};LGraphFXLens.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 d=this.properties.aberration;this.isInputConnected(1)&&(d=this.getInputData(1),this.properties.aberration=d);var f=this.properties.distortion;this.isInputConnected(2)&& +(f=this.getInputData(2),this.properties.distortion=f);var h=this.properties.blur;this.isInputConnected(3)&&(h=this.getInputData(3),this.properties.blur=h);gl.disable(gl.BLEND);gl.disable(gl.DEPTH_TEST);var n=Mesh.getScreenQuad(),l=LGraphFXLens._shader;this._tex.drawTo(function(){e.bind(0);l.uniforms({u_texture:0,u_aberration:d,u_distortion:f,u_blur:h}).draw(n)});this.setOutputData(0,this._tex)}};LGraphFXLens.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"; LiteGraph.registerNodeType("fx/lens",LGraphFXLens);window.LGraphFXLens=LGraphFXLens;var LGraphFXBokeh=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}};LGraphFXBokeh.title="Bokeh";LGraphFXBokeh.desc="applies an Bokeh effect";LGraphFXBokeh.widgets_info={shape:{widget:"texture"}};LGraphFXBokeh.prototype.onExecute= -function(){var d=this.getInputData(0),e=this.getInputData(1),f=this.getInputData(2);if(d&&f&&this.properties.shape){e||(e=d);var k=LGraphTexture.getTexture(this.properties.shape);if(k){var h=this.properties.threshold;this.isInputConnected(3)&&(h=this.getInputData(3),this.properties.threshold=h);var a=gl.UNSIGNED_BYTE;this.properties.high_precision&&(a=gl.half_float_ext?gl.HALF_FLOAT_OES:gl.FLOAT);this._temp_texture&&this._temp_texture.type==a&&this._temp_texture.width==d.width&&this._temp_texture.height== -d.height||(this._temp_texture=new GL.Texture(d.width,d.height,{type:a,format:gl.RGBA,filter:gl.LINEAR}));var b=LGraphFXBokeh._first_shader;b||(b=LGraphFXBokeh._first_shader=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,LGraphFXBokeh._first_pixel_shader));var c=LGraphFXBokeh._second_shader;c||(c=LGraphFXBokeh._second_shader=new GL.Shader(LGraphFXBokeh._second_vertex_shader,LGraphFXBokeh._second_pixel_shader));var g=this._points_mesh;g&&g._width==d.width&&g._height==d.height&&2==g._spacing||(g=this.createPointsMesh(d.width, -d.height,2));var l=Mesh.getScreenQuad(),v=this.properties.size,t=this.properties.alpha;gl.disable(gl.DEPTH_TEST);gl.disable(gl.BLEND);this._temp_texture.drawTo(function(){d.bind(0);e.bind(1);f.bind(2);b.uniforms({u_texture:0,u_texture_blur:1,u_mask:2,u_texsize:[d.width,d.height]}).draw(l)});this._temp_texture.drawTo(function(){gl.enable(gl.BLEND);gl.blendFunc(gl.ONE,gl.ONE);d.bind(0);k.bind(3);c.uniforms({u_texture:0,u_mask:2,u_shape:3,u_alpha:t,u_threshold:h,u_pointSize:v,u_itexsize:[1/d.width,1/ -d.height]}).draw(g,gl.POINTS)});this.setOutputData(0,this._temp_texture)}}else this.setOutputData(0,d)};LGraphFXBokeh.prototype.createPointsMesh=function(d,e,f){for(var k=Math.round(d/f),h=Math.round(e/f),a=new Float32Array(k*h*2),b=-1,c=2/d*f,g=2/e*f,l=0;l=e.NOTEON||b<=e.NOTEOFF)this.channel=a&15};Object.defineProperty(e.prototype,"velocity",{get:function(){return this.cmd==e.NOTEON?this.data[2]:-1},set:function(a){this.data[2]=a},enumerable:!0}); -e.notes="A A# B C C# D D# E F F# G G#".split(" ");e.prototype.getPitch=function(){return 440*Math.pow(2,(this.data[1]-69)/12)};e.computePitch=function(a){return 440*Math.pow(2,(a-69)/12)};e.prototype.getPitchBend=function(){return this.data[1]+(this.data[2]<<7)-8192};e.computePitchBend=function(a,b){return a+(b<<7)-8192};e.prototype.setCommandFromString=function(a){this.cmd=e.computeCommandFromString(a)};e.computeCommandFromString=function(a){if(!a)return 0;if(a&&a.constructor===Number)return a;a= -a.toUpperCase();switch(a){case "NOTE ON":case "NOTEON":return e.NOTEON;case "NOTE OFF":case "NOTEOFF":return e.NOTEON;case "KEY PRESSURE":case "KEYPRESSURE":return e.KEYPRESSURE;case "CONTROLLER CHANGE":case "CONTROLLERCHANGE":case "CC":return e.CONTROLLERCHANGE;case "PROGRAM CHANGE":case "PROGRAMCHANGE":case "PC":return e.PROGRAMCHANGE;case "CHANNEL PRESSURE":case "CHANNELPRESSURE":return e.CHANNELPRESSURE;case "PITCH BEND":case "PITCHBEND":return e.PITCHBEND;case "TIME TICK":case "TIMETICK":return e.TIMETICK; -default:return Number(a)}};e.toNoteString=function(a){var b;b=(a-21)%12;0>b&&(b=12+b);return e.notes[b]+Math.floor((a-24)/12+1)};e.prototype.toString=function(){var a=""+this.channel+". ";switch(this.cmd){case e.NOTEON:a+="NOTEON "+e.toNoteString(this.data[1]);break;case e.NOTEOFF:a+="NOTEOFF "+e.toNoteString(this.data[1]);break;case e.CONTROLLERCHANGE:a+="CC "+this.data[1]+" "+this.data[2];break;case e.PROGRAMCHANGE:a+="PC "+this.data[1];break;case e.PITCHBEND:a+="PITCHBEND "+this.getPitchBend(); -break;case e.KEYPRESSURE:a+="KEYPRESS "+this.data[1]}return a};e.prototype.toHexString=function(){for(var a="",b=0;bthis.properties.max_value||this.trigger("on_midi",b)};LiteGraph.registerNodeType("midi/filter", -b);c.title="MIDIEvent";c.desc="Create a MIDI Event";c.prototype.onAction=function(a,b){"assign"==a?(this.properties.channel=b.channel,this.properties.cmd=b.cmd,this.properties.value1=b.data[1],this.properties.value2=b.data[2]):(b=new e,b.channel=this.properties.channel,this.properties.cmd&&this.properties.cmd.constructor===String?b.setCommandFromString(this.properties.cmd):b.cmd=this.properties.cmd,b.data[0]=b.cmd|b.channel,b.data[1]=Number(this.properties.value1),b.data[2]=Number(this.properties.value2), -this.trigger("on_midi",b))};c.prototype.onExecute=function(){var a=this.properties;if(this.outputs)for(var b=0;b=this.size[0]&&(e=this.size[0]-1),a.strokeStyle="red",a.beginPath(),a.moveTo(e,d),a.lineTo(e,0),a.stroke())}};v.title="Visualization";v.desc="Audio Visualization";LiteGraph.registerNodeType("audio/visualization",v);t.prototype.onExecute=function(){if(this._freqs=this.getInputData(0)){var a=this.properties.band,b=this.getInputData(1);void 0!==b&&(a=b);b=n.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)}};t.prototype.onGetInputs=function(){return[["band","number"]]};t.title="Signal";t.desc="extract the signal of some frequency";LiteGraph.registerNodeType("audio/signal",t);u.title="Destination";u.desc="Audio output";LiteGraph.registerNodeType("audio/destination",u)})(window); +(function(e){function d(a){this.cmd=this.channel=0;a?this.setup(a):this.data=[0,0,0]}function f(a,b){navigator.requestMIDIAccess?(this.on_ready=a,this.state={note:[],cc:[]},navigator.requestMIDIAccess().then(this.onMIDISuccess.bind(this),this.onMIDIFailure.bind(this))):(this.error="not suppoorted",b?b("Not supported"):console.error("MIDI NOT SUPPORTED, enable by chrome://flags"))}function h(){this.addOutput("on_midi",LiteGraph.EVENT);this.addOutput("out","midi");this.properties={port:0};this._current_midi_event= +this._last_midi_event=null;var a=this;new f(function(b){a._midi=b;if(a._waiting)a.onStart();a._waiting=!1})}function n(){this.addInput("send",LiteGraph.EVENT);this.properties={port:0};var a=this;new f(function(b){a._midi=b})}function l(){this.addInput("on_midi",LiteGraph.EVENT);this._str="";this.size=[200,40]}function a(){this.properties={channel:-1,cmd:-1,min_value:-1,max_value:-1};this.addInput("in",LiteGraph.EVENT);this.addOutput("on_midi",LiteGraph.EVENT)}function b(){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)}function c(){this.properties={cc:1,value:0};this.addOutput("value","number")}d.prototype.setup=function(a){this.data=a;this.status=a=a[0];var b=a&240;this.cmd=240<=a?a:b;this.cmd==d.NOTEON&&0==this.velocity&&(this.cmd=d.NOTEOFF);this.cmd_str=d.commands[this.cmd]||"";if(b>=d.NOTEON||b<=d.NOTEOFF)this.channel=a&15};Object.defineProperty(d.prototype,"velocity", +{get:function(){return this.cmd==d.NOTEON?this.data[2]:-1},set:function(a){this.data[2]=a},enumerable:!0});d.notes="A A# B C C# D D# E F F# G G#".split(" ");d.prototype.getPitch=function(){return 440*Math.pow(2,(this.data[1]-69)/12)};d.computePitch=function(a){return 440*Math.pow(2,(a-69)/12)};d.prototype.getCC=function(){return this.data[1]};d.prototype.getCCValue=function(){return this.data[2]};d.prototype.getPitchBend=function(){return this.data[1]+(this.data[2]<<7)-8192};d.computePitchBend=function(a, +b){return a+(b<<7)-8192};d.prototype.setCommandFromString=function(a){this.cmd=d.computeCommandFromString(a)};d.computeCommandFromString=function(a){if(!a)return 0;if(a&&a.constructor===Number)return a;a=a.toUpperCase();switch(a){case "NOTE ON":case "NOTEON":return d.NOTEON;case "NOTE OFF":case "NOTEOFF":return d.NOTEON;case "KEY PRESSURE":case "KEYPRESSURE":return d.KEYPRESSURE;case "CONTROLLER CHANGE":case "CONTROLLERCHANGE":case "CC":return d.CONTROLLERCHANGE;case "PROGRAM CHANGE":case "PROGRAMCHANGE":case "PC":return d.PROGRAMCHANGE; +case "CHANNEL PRESSURE":case "CHANNELPRESSURE":return d.CHANNELPRESSURE;case "PITCH BEND":case "PITCHBEND":return d.PITCHBEND;case "TIME TICK":case "TIMETICK":return d.TIMETICK;default:return Number(a)}};d.toNoteString=function(a){var b;b=(a-21)%12;0>b&&(b=12+b);return d.notes[b]+Math.floor((a-24)/12+1)};d.prototype.toString=function(){var a=""+this.channel+". ";switch(this.cmd){case d.NOTEON:a+="NOTEON "+d.toNoteString(this.data[1]);break;case d.NOTEOFF:a+="NOTEOFF "+d.toNoteString(this.data[1]); +break;case d.CONTROLLERCHANGE:a+="CC "+this.data[1]+" "+this.data[2];break;case d.PROGRAMCHANGE:a+="PC "+this.data[1];break;case d.PITCHBEND:a+="PITCHBEND "+this.getPitchBend();break;case d.KEYPRESSURE:a+="KEYPRESS "+this.data[1]}return a};d.prototype.toHexString=function(){for(var a="",b=0;bthis.properties.max_value||this.trigger("on_midi", +b)};LiteGraph.registerNodeType("midi/filter",a);b.title="MIDIEvent";b.desc="Create a MIDI Event";b.prototype.onAction=function(a,b){"assign"==a?(this.properties.channel=b.channel,this.properties.cmd=b.cmd,this.properties.value1=b.data[1],this.properties.value2=b.data[2]):(b=new d,b.channel=this.properties.channel,this.properties.cmd&&this.properties.cmd.constructor===String?b.setCommandFromString(this.properties.cmd):b.cmd=this.properties.cmd,b.data[0]=b.cmd|b.channel,b.data[1]=Number(this.properties.value1), +b.data[2]=Number(this.properties.value2),this.trigger("on_midi",b))};b.prototype.onExecute=function(){var a=this.properties;if(this.outputs)for(var b=0;b=this.size[0]&&(e=this.size[0]-1),a.strokeStyle="red",a.beginPath(),a.moveTo(e,d),a.lineTo(e,0),a.stroke())}}; +u.title="Visualization";u.desc="Audio Visualization";LiteGraph.registerNodeType("audio/visualization",u);t.prototype.onExecute=function(){if(this._freqs=this.getInputData(0)){var a=this.properties.band,b=this.getInputData(1);void 0!==b&&(a=b);b=p.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)}};t.prototype.onGetInputs=function(){return[["band", +"number"]]};t.title="Signal";t.desc="extract the signal of some frequency";LiteGraph.registerNodeType("audio/signal",t);r.prototype.onAdded=function(a){a.status==LGraph.STATUS_RUNNING&&(this.audionode.onaudioprocess=this._callback)};r["@code"]={widget:"code"};r.prototype.onStart=function(){this.audionode.onaudioprocess=this._callback};r.prototype.onStop=function(){this.audionode.onaudioprocess=r._bypass_function};r.prototype.onPause=function(){this.audionode.onaudioprocess=r._bypass_function};r.prototype.onUnpause= +function(){this.audionode.onaudioprocess=this._callback};r.prototype.onExecute=function(){};r.prototype.onRemoved=function(){this.audionode.onaudioprocess=r._bypass_function};r.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=r._bypass_function,this.audionode.onaudioprocess=this._callback}}; +r.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))};r.default_function=function(){this.onaudioprocess=function(a){var b=a.inputBuffer;a=a.outputBuffer;for(var c=0;c -1 && slot < this.outputs.length && this.outputs[slot] && this.outputs[slot].links != null) + 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++) { @@ -1597,7 +1678,7 @@ LGraphNode.prototype.isInputConnected = function(slot) * tells you info about an input connection (which node, type, etc) * @method getInputInfo * @param {number} slot -* @return {Object} object or null +* @return {Object} object or null { link: id, name: string, type: string or 0 } */ LGraphNode.prototype.getInputInfo = function(slot) { @@ -1629,12 +1710,29 @@ LGraphNode.prototype.getInputNode = function( slot ) return this.graph.getNodeById( link_info.origin_id ); } +/** +* 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 +* @return {Object} object or null { name: string, type: string, links: [ ids of links in number ] } */ LGraphNode.prototype.getOutputInfo = function(slot) { @@ -2038,7 +2136,8 @@ LGraphNode.prototype.getSlotInPosition = function( x, y ) */ LGraphNode.prototype.findInputSlot = function(name) { - if(!this.inputs) return -1; + if(!this.inputs) + return -1; for(var i = 0, l = this.inputs.length; i < l; ++i) if(name == this.inputs[i].name) return i; @@ -2145,10 +2244,11 @@ LGraphNode.prototype.connect = function( slot, target_node, target_slot ) var input = target_node.inputs[target_slot]; - if( LiteGraph.isValidConnection( output.type, input.type) ) + if( LiteGraph.isValidConnection( output.type, input.type ) ) { var link_info = { - id: this.graph.last_link_id++, + id: this.graph.last_link_id++, + type: input.type, origin_id: this.id, origin_slot: slot, target_id: target_node.id, @@ -2166,9 +2266,9 @@ LGraphNode.prototype.connect = function( slot, target_node, target_slot ) target_node.inputs[target_slot].link = link_info.id; if(this.onConnectionsChange) - this.onConnectionsChange( LiteGraph.OUTPUT, slot, true, link_info ); //link_info has been created now, so its updated + 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 ); + target_node.onConnectionsChange( LiteGraph.INPUT, target_slot, true, link_info, input ); } this.setDirtyCanvas(false,true); @@ -2225,12 +2325,13 @@ LGraphNode.prototype.disconnectOutput = function( slot, target_node ) if( link_info.target_id == target_node.id ) { output.links.splice(i,1); //remove here - target_node.inputs[ link_info.target_slot ].link = null; //remove there + 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 ); //link_info hasnt been modified so its ok + 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 ); + this.onConnectionsChange( LiteGraph.OUTPUT, slot, false, link_info, output ); break; } } @@ -2243,13 +2344,17 @@ LGraphNode.prototype.disconnectOutput = function( slot, target_node ) var link_info = this.graph.links[ link_id ]; var target_node = this.graph.getNodeById( link_info.target_id ); + var input = null; if(target_node) - target_node.inputs[ link_info.target_slot ].link = null; //remove other side link + { + input = target_node.inputs[ link_info.target_slot ]; + input.link = null; //remove other side link + } 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 ); //link_info hasnt been modified so its ok + 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 ); + this.onConnectionsChange( LiteGraph.OUTPUT, slot, false, link_info, output ); } output.links = null; } @@ -2318,9 +2423,9 @@ LGraphNode.prototype.disconnectInput = function( slot ) } if( this.onConnectionsChange ) - this.onConnectionsChange( LiteGraph.INPUT, slot, false, link_info ); + this.onConnectionsChange( LiteGraph.INPUT, slot, false, link_info, input ); if( target_node.onConnectionsChange ) - target_node.onConnectionsChange( LiteGraph.OUTPUT, i, false, link_info ); + target_node.onConnectionsChange( LiteGraph.OUTPUT, i, false, link_info, output ); } this.setDirtyCanvas(false,true); @@ -2506,7 +2611,7 @@ LGraphNode.prototype.localToScreen = function(x,y, graphcanvas) * * @class LGraphCanvas * @constructor -* @param {HTMLCanvas} canvas the canvas where you want to render (it accepts a selector in string format or the canvas itself) +* @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 } */ @@ -2546,7 +2651,7 @@ global.LGraphCanvas = LiteGraph.LGraphCanvas = function LGraphCanvas( canvas, gr this.render_curved_connections = true; this.render_connection_arrows = true; - this.connections_width = 4; + this.connections_width = 3; //link canvas and graph if(graph) @@ -2676,7 +2781,7 @@ LGraphCanvas.prototype.closeSubgraph = function() * assigns a canvas * * @method setCanvas -* @param {Canvas} assigns a canvas +* @param {Canvas} assigns a canvas (also accepts the ID of the element (not a selector) */ LGraphCanvas.prototype.setCanvas = function( canvas, skip_events ) { @@ -2722,6 +2827,8 @@ LGraphCanvas.prototype.setCanvas = function( canvas, skip_events ) 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"); } @@ -2983,6 +3090,7 @@ LGraphCanvas.prototype.processMouseDown = function(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 ); @@ -3164,6 +3272,7 @@ LGraphCanvas.prototype.processMouseMove = function(e) 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]]; @@ -3311,6 +3420,7 @@ LGraphCanvas.prototype.processMouseUp = function(e) 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 ); @@ -3928,28 +4038,21 @@ LGraphCanvas.prototype.drawFrontCanvas = function() default: link_color = "#AFA"; } - this.renderLink(ctx, this.connecting_pos, [this.canvas_mouse[0],this.canvas_mouse[1]], 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 ); 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); - - /* - if( this.connecting_output.round) - ctx.arc( this.connecting_pos[0], this.connecting_pos[1],4,0,Math.PI*2); - else - ctx.rect( this.connecting_pos[0], this.connecting_pos[1],12,6); - */ + 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.arc( this._highlight_input[0], this._highlight_input[1],6,0,Math.PI*2); ctx.fill(); } } @@ -4493,22 +4596,21 @@ LGraphCanvas.prototype.drawConnections = function(ctx) else start_node_slotpos = start_node.getConnectionPos(false, start_node_slot); - var color = LGraphCanvas.link_type_colors[ node.inputs[i].type ] || this.default_link_color; + this.renderLink( ctx, start_node_slotpos, node.getConnectionPos(true,i), link ); - this.renderLink(ctx, start_node_slotpos, node.getConnectionPos(true,i), color ); - - if(link && link._last_time && now - link._last_time < 1000 ) + //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) , color, true, f ); + this.renderLink( ctx, start_node_slotpos, node.getConnectionPos(true,i), link, true, f, color ); } } } ctx.globalAlpha = 1; } -LGraphCanvas.prototype.renderLink = function(ctx,a,b,color, skip_border, flow ) +LGraphCanvas.prototype.renderLink = function( ctx, a, b, link, skip_border, flow, color ) { if(!this.highquality_render) { @@ -4524,6 +4626,13 @@ LGraphCanvas.prototype.renderLink = function(ctx,a,b,color, skip_border, flow ) 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; + + //begin line shape ctx.beginPath(); if(this.render_curved_connections) //splines @@ -4541,6 +4650,7 @@ LGraphCanvas.prototype.renderLink = function(ctx,a,b,color, skip_border, flow ) 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)"; @@ -4550,35 +4660,39 @@ LGraphCanvas.prototype.renderLink = function(ctx,a,b,color, skip_border, flow ) ctx.lineWidth = this.connections_width; ctx.fillStyle = ctx.strokeStyle = color; ctx.stroke(); + //end line shape - //no symbols - if(!this.render_connection_arrows || this.scale < 0.6) - return; - - //render arrow - if(this.render_connection_arrows && this.scale > 0.6) + //render arrow in the middle + if( this.render_connection_arrows && this.scale >= 0.6 ) { - var pos = this.computeConnectionPoint(a,b,0.5); - var pos2 = this.computeConnectionPoint(a,b,0.51); + //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); - //get two points in the bezier curve - 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; + //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; - 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 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) @@ -4719,19 +4833,20 @@ LGraphCanvas.prototype.touchHandler = function(event) /* CONTEXT MENU ********************/ -LGraphCanvas.onMenuAdd = function(node, e, prev_menu, canvas, first_event ) +LGraphCanvas.onMenuAdd = function( node, options, e, prev_menu ) { + var canvas = LGraphCanvas.active_canvas; var ref_window = canvas.getCanvasWindow(); var values = LiteGraph.getNodeTypesCategories(); - var entries = {}; + var entries = []; for(var i in values) if(values[i]) - entries[ i ] = { value: values[i], content: values[i] , is_menu: true }; + entries.push({ value: values[i], content: values[i], has_submenu: true }); - var menu = LiteGraph.createContextMenu(entries, {event: e, callback: inner_clicked, from: prev_menu}, ref_window); + var menu = new LiteGraph.ContextMenu( entries, { event: e, callback: inner_clicked, parentMenu: prev_menu }, ref_window); - function inner_clicked(v, e) + function inner_clicked( v, option, e ) { var category = v.value; var node_types = LiteGraph.getNodeTypesInCategory(category); @@ -4739,16 +4854,17 @@ LGraphCanvas.onMenuAdd = function(node, e, prev_menu, canvas, first_event ) for(var i in node_types) values.push( { content: node_types[i].title, value: node_types[i].type }); - LiteGraph.createContextMenu(values, {event: e, callback: inner_create, from: menu}, ref_window); + new LiteGraph.ContextMenu( values, {event: e, callback: inner_create, parentMenu: menu }, ref_window); return false; } - function inner_create(v, e) + 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); + node.pos = canvas.convertEventToCanvas( first_event ); canvas.graph.add( node ); } } @@ -4767,13 +4883,14 @@ LGraphCanvas.onMenuNodeEdit = function() } -LGraphCanvas.showMenuNodeInputs = function(node, e, prev_menu) +LGraphCanvas.showMenuNodeOptionalInputs = function( v, options, e, prev_menu, node ) { if(!node) return; var that = this; - var ref_window = this.getCanvasWindow(); + var canvas = LGraphCanvas.active_canvas; + var ref_window = canvas.getCanvasWindow(); var options = node.optional_inputs; if(node.onGetInputs) @@ -4801,7 +4918,7 @@ LGraphCanvas.showMenuNodeInputs = function(node, e, prev_menu) if(!entries.length) return; - var menu = LiteGraph.createContextMenu(entries, {event: e, callback: inner_clicked, from: prev_menu}, ref_window); + var menu = new LiteGraph.ContextMenu(entries, { event: e, callback: inner_clicked, parentMenu: prev_menu, node: node }, ref_window); function inner_clicked(v, e, prev) { @@ -4809,7 +4926,7 @@ LGraphCanvas.showMenuNodeInputs = function(node, e, prev_menu) return; if(v.callback) - v.callback.call(that, node, v, e, prev); + v.callback.call( that, node, v, e, prev ); if(v.value) node.addInput(v.value[0],v.value[1], v.value[2]); @@ -4818,13 +4935,14 @@ LGraphCanvas.showMenuNodeInputs = function(node, e, prev_menu) return false; } -LGraphCanvas.showMenuNodeOutputs = function(node, e, prev_menu) +LGraphCanvas.showMenuNodeOptionalOutputs = function( v, options, e, prev_menu, node ) { if(!node) return; var that = this; - var ref_window = this.getCanvasWindow(); + var canvas = LGraphCanvas.active_canvas; + var ref_window = canvas.getCanvasWindow(); var options = node.optional_outputs; if(node.onGetOutputs) @@ -4858,7 +4976,7 @@ LGraphCanvas.showMenuNodeOutputs = function(node, e, prev_menu) if(!entries.length) return; - var menu = LiteGraph.createContextMenu(entries, {event: e, callback: inner_clicked, from: prev_menu}, ref_window); + var menu = new LiteGraph.ContextMenu(entries, {event: e, callback: inner_clicked, parentMenu: prev_menu, node: node }, ref_window); function inner_clicked( v, e, prev ) { @@ -4866,7 +4984,7 @@ LGraphCanvas.showMenuNodeOutputs = function(node, e, prev_menu) return; if(v.callback) - v.callback.call(that, node, v, e, prev); + v.callback.call( that, node, v, e, prev ); if(!v.value) return; @@ -4877,24 +4995,25 @@ LGraphCanvas.showMenuNodeOutputs = function(node, e, prev_menu) { var entries = []; for(var i in value) - entries.push({content: i, value: value[i]}); - LiteGraph.createContextMenu(entries, {event: e, callback: inner_clicked, from: prev_menu}); + 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.addOutput( v.value[0], v.value[1], v.value[2]); } return false; } -LGraphCanvas.onShowMenuNodeProperties = function(node,e, prev_menu) +LGraphCanvas.onShowMenuNodeProperties = function( value, options, e, prev_menu, node ) { if(!node || !node.properties) return; var that = this; - var ref_window = this.getCanvasWindow(); + var canvas = LGraphCanvas.active_canvas; + var ref_window = canvas.getCanvasWindow(); var entries = []; for (var i in node.properties) @@ -4907,13 +5026,14 @@ LGraphCanvas.onShowMenuNodeProperties = function(node,e, prev_menu) if(!entries.length) return; - var menu = LiteGraph.createContextMenu(entries, {event: e, callback: inner_clicked, from: prev_menu, allow_html: true },ref_window); + 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, e, prev ) + function inner_clicked( v, options, e, prev ) { if(!node) return; - that.showEditPropertyValue( node, v.value, { event: e }); + var rect = this.getBoundingClientRect(); + canvas.showEditPropertyValue( node, v.value, { position: [rect.left, rect.top] }); } return false; @@ -4926,7 +5046,16 @@ LGraphCanvas.decodeHTML = function( str ) return e.innerHTML; } -LGraphCanvas.onShowTitleEditor = function( node, event ) +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 = ""; @@ -4946,7 +5075,10 @@ LGraphCanvas.onShowTitleEditor = function( node, event ) }); } - var rect = this.canvas.getClientRects()[0]; + var graphcanvas = LGraphCanvas.active_canvas; + var canvas = graphcanvas.canvas; + + var rect = canvas.getBoundingClientRect(); var offsetx = -20; var offsety = -20; if(rect) @@ -4962,13 +5094,13 @@ LGraphCanvas.onShowTitleEditor = function( node, event ) } else { - dialog.style.left = (this.canvas.width * 0.5 + offsetx) + "px"; - dialog.style.top = (this.canvas.height * 0.5 + offsety) + "px"; + 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 ); - this.canvas.parentNode.appendChild( dialog ); + canvas.parentNode.appendChild( dialog ); function inner() { @@ -5082,16 +5214,24 @@ LGraphCanvas.prototype.showEditPropertyValue = function( node, property, options offsety -= rect.top; } - if( options.event ) + if( options.position ) { - dialog.style.left = (options.event.pageX + offsetx) + "px"; - dialog.style.top = (options.event.pageY + offsety)+ "px"; - } - else + offsetx += options.position[0]; + offsety += options.position[1]; + } + else if( options.event ) { - dialog.style.left = (this.canvas.width * 0.5 + offsetx) + "px"; - dialog.style.top = (this.canvas.height * 0.5 + offsety) + "px"; + 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"; var button = dialog.querySelector("button"); button.addEventListener("click", inner ); @@ -5117,20 +5257,20 @@ LGraphCanvas.prototype.showEditPropertyValue = function( node, property, options } } -LGraphCanvas.onMenuNodeCollapse = function(node) +LGraphCanvas.onMenuNodeCollapse = function( value, options, e, menu, node ) { node.flags.collapsed = !node.flags.collapsed; node.setDirtyCanvas(true,true); } -LGraphCanvas.onMenuNodePin = function(node) +LGraphCanvas.onMenuNodePin = function( value, options, e, menu, node ) { node.pin(); } -LGraphCanvas.onMenuNodeMode = function(node, e, prev_menu) +LGraphCanvas.onMenuNodeMode = function( value, options, e, menu, node ) { - LiteGraph.createContextMenu(["Always","On Event","Never"], {event: e, callback: inner_clicked, from: prev_menu}); + new LiteGraph.ContextMenu(["Always","On Event","Never"], {event: e, callback: inner_clicked, parentMenu: prev_menu, node: node }); function inner_clicked(v) { @@ -5149,8 +5289,11 @@ LGraphCanvas.onMenuNodeMode = function(node, e, prev_menu) return false; } -LGraphCanvas.onMenuNodeColors = function(node, e, prev_menu) +LGraphCanvas.onMenuNodeColors = function( value, options, e, menu, node ) { + if(!node) + throw("no node for color"); + var values = []; for(var i in LGraphCanvas.node_colors) { @@ -5158,12 +5301,14 @@ LGraphCanvas.onMenuNodeColors = function(node, e, prev_menu) var value = {value:i, content:""+i+""}; values.push(value); } - LiteGraph.createContextMenu(values, {event: e, callback: inner_clicked, from: prev_menu}); + 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(!node) + return; + + var color = LGraphCanvas.node_colors[ v.value ]; if(color) { node.color = color.color; @@ -5175,13 +5320,17 @@ LGraphCanvas.onMenuNodeColors = function(node, e, prev_menu) return false; } -LGraphCanvas.onMenuNodeShapes = function(node,e) +LGraphCanvas.onMenuNodeShapes = function( value, options, e, menu, node ) { - LiteGraph.createContextMenu(["box","round"], {event: e, callback: inner_clicked}); + if(!node) + throw("no node passed"); + + new LiteGraph.ContextMenu(["box","round"], { event: e, callback: inner_clicked, parentMenu: menu, node: node }); function inner_clicked(v) { - if(!node) return; + if(!node) + return; node.shape = v; node.setDirtyCanvas(true); } @@ -5189,14 +5338,18 @@ LGraphCanvas.onMenuNodeShapes = function(node,e) return false; } -LGraphCanvas.onMenuNodeRemove = function(node) +LGraphCanvas.onMenuNodeRemove = function( value, options, e, menu, node ) { - if(node.removable == false) return; + if(!node) + throw("no node passed"); + + if(node.removable == false) + return; node.graph.remove(node); node.setDirtyCanvas(true,true); } -LGraphCanvas.onMenuNodeClone = function(node) +LGraphCanvas.onMenuNodeClone = function( value, options, e, menu, node ) { if(node.clonable == false) return; var newnode = node.clone(); @@ -5221,7 +5374,7 @@ LGraphCanvas.prototype.getCanvasMenuOptions = function() else { options = [ - {content:"Add Node", is_menu: true, callback: LGraphCanvas.onMenuAdd } + { content:"Add Node", has_submenu: true, callback: LGraphCanvas.onMenuAdd } //{content:"Collapse All", callback: LGraphCanvas.onMenuCollapseAll } ]; @@ -5239,7 +5392,8 @@ LGraphCanvas.prototype.getCanvasMenuOptions = function() return options; } -LGraphCanvas.prototype.getNodeMenuOptions = function(node) +//called by processContextMenu to extract the menu list +LGraphCanvas.prototype.getNodeMenuOptions = function( node ) { var options = null; @@ -5247,17 +5401,18 @@ LGraphCanvas.prototype.getNodeMenuOptions = function(node) options = node.getMenuOptions(this); else options = [ - {content:"Inputs", is_menu: true, disabled:true, callback: LGraphCanvas.showMenuNodeInputs }, - {content:"Outputs", is_menu: true, disabled:true, callback: LGraphCanvas.showMenuNodeOutputs }, + {content:"Inputs", has_submenu: true, disabled:true, callback: LGraphCanvas.showMenuNodeOptionalInputs }, + {content:"Outputs", has_submenu: true, disabled:true, callback: LGraphCanvas.showMenuNodeOptionalOutputs }, null, - {content:"Properties", is_menu: true, callback: LGraphCanvas.onShowMenuNodeProperties }, + {content:"Properties", has_submenu: true, callback: LGraphCanvas.onShowMenuNodeProperties }, null, {content:"Title", callback: LGraphCanvas.onShowTitleEditor }, - {content:"Mode", is_menu: true, callback: LGraphCanvas.onMenuNodeMode }, + {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", is_menu: true, callback: LGraphCanvas.onMenuNodeColors }, - {content:"Shapes", is_menu: true, callback: LGraphCanvas.onMenuNodeShapes }, + {content:"Colors", has_submenu: true, callback: LGraphCanvas.onMenuNodeColors }, + {content:"Shapes", has_submenu: true, callback: LGraphCanvas.onMenuNodeShapes }, null ]; @@ -5290,21 +5445,28 @@ LGraphCanvas.prototype.getNodeMenuOptions = function(node) options[1].disabled = false; } + if(node.graph && node.graph.onGetNodeMenuOptions ) + node.graph.onGetNodeMenuOptions( options, node ); + return options; } -LGraphCanvas.prototype.processContextMenu = function(node, event) +LGraphCanvas.prototype.processContextMenu = function( node, event ) { var that = this; - var win = this.getCanvasWindow(); + var canvas = LGraphCanvas.active_canvas; + var ref_window = canvas.getCanvasWindow(); var menu_info = null; - var options = {event: event, callback: inner_option_clicked}; + 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) { @@ -5316,14 +5478,13 @@ LGraphCanvas.prototype.processContextMenu = function(node, event) else menu_info = node ? this.getNodeMenuOptions(node) : this.getCanvasMenuOptions(); - //show menu if(!menu_info) return; - var menu = LiteGraph.createContextMenu( menu_info, options, win); + var menu = new LiteGraph.ContextMenu( menu_info, options, ref_window ); - function inner_option_clicked(v,e) + function inner_option_clicked( v, options, e ) { if(!v) return; @@ -5337,8 +5498,8 @@ LGraphCanvas.prototype.processContextMenu = function(node, event) return; } - if(v.callback) - return v.callback.call(that, node, e, menu, that, event ); + //if(v.callback) + // return v.callback.call(that, node, options, e, menu, that, event ); } } @@ -5478,195 +5639,332 @@ function num2hex(triplet) { LiteGraph.num2hex = num2hex; -/* LiteGraph GUI elements *************************************/ +/* LiteGraph GUI elements used for canvas editing *************************************/ -LiteGraph.createContextMenu = function(values, options, ref_window) +/** +* 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; - //allows to create graph canvas in separate window - ref_window = ref_window || window; + //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); - if (!options.from) - LiteGraph.closeAllContextMenus( ref_window ); - else { - //closing submenus - var menus = document.querySelectorAll(".graphcontextmenu"); - for (var key in menus) { - if (menus[key].previousSibling == options.from) - menus[key].closeMenu(); - } - } - var root = ref_window.document.createElement("div"); - root.className = "graphcontextmenu graphmenubar-panel"; this.root = root; - var style = root.style; - - style.minWidth = "100px"; - style.minHeight = "20px"; - - style.position = "fixed"; - style.top = "100px"; - style.left = "100px"; - style.color = "#AAF"; - style.padding = "2px"; - style.borderBottom = "2px solid #AAF"; - style.backgroundColor = "#444"; - style.zIndex = 10; //title if(options.title) { var element = document.createElement("div"); - element.className = "graphcontextmenu-title"; + element.className = "litemenu-title"; element.innerHTML = options.title; root.appendChild(element); } - //avoid a context menu in a context menu - root.addEventListener("contextmenu", function(e) { e.preventDefault(); return false; }); - + //entries + var num = 0; for(var i in values) { - var item = values[i]; - var element = ref_window.document.createElement("div"); - element.className = "graphmenu-entry"; - - if(item == null) - { - element.className += " separator"; - root.appendChild(element); - continue; - } - - if(item.is_menu) - element.className += " submenu"; - - if(item.disabled) - element.className += " disabled"; - - if(item.className) - element.className += " " + item.className; - - element.style.cursor = "pointer"; - element.dataset["value"] = typeof(item) == "string" ? item : item.value; - element.data = item; - - var content = ""; - if(typeof(item) == "string") - content = values.constructor == Array ? values[i] : i; - else - content = item.content ? item.content : i; - if(options.allow_html) - element.innerHTML = content; - else - element.innerText = content; - - element.addEventListener("click", on_click ); - root.appendChild(element); + 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++; } - root.addEventListener("mouseover", function(e) { - this.mouse_inside = true; - }); - - root.addEventListener("mouseout", function(e) { - //console.log("OUT!"); - //check if mouse leave a inner element - var aux = e.relatedTarget || e.toElement; - while(aux != this && aux != ref_window.document) - aux = aux.parentNode; - - if(aux == this) + //close on leave + root.addEventListener("mouseleave", function(e) { + if(that.lock) return; - this.mouse_inside = false; - if(!this.block_close) - this.closeMenu(); + that.close(e); }); //insert before checking position - ref_window.document.body.appendChild(root); + var root_document = document; + if(options.event) + root_document = options.event.target.ownerDocument; - var root_rect = root.getClientRects()[0]; - - //link menus - if(options.from) - { - options.from.block_close = true; - } + 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.left) - left = options.left; + if(options.title) + top -= 20; - var rect = ref_window.document.body.getClientRects()[0]; - - if(options.from) + if(options.parentMenu) { - var parent_rect = options.from.getClientRects()[0]; - left = parent_rect.left + parent_rect.width; + var rect = options.parentMenu.root.getBoundingClientRect(); + left = rect.left + rect.width; } - - if(left > (rect.width - root_rect.width - 10)) - left = (rect.width - root_rect.width - 10); - if(top > (rect.height - root_rect.height - 10)) - top = (rect.height - root_rect.height - 10); + 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"; +} - function on_click(e) { - var value = this.dataset["value"]; - var close = true; - if(options.callback) +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) { - var ret = options.callback.call(root, this.data, e ); - if( ret !== undefined ) close = ret; + if(value.disabled) + { + disabled = true; + element.classList.add("disabled"); + } + if(value.submenu || value.has_submenu) + element.classList.add("has_submenu"); } - if(close) - LiteGraph.closeAllContextMenus( ref_window ); - //root.closeMenu(); + if(typeof(value) == "function") + { + element.dataset["value"] = name; + element.onclick_callback = value; + } + else + element.dataset["value"] = value; } - root.closeMenu = function() - { - if(options.from) - { - options.from.block_close = false; - if(!options.from.mouse_inside) - options.from.closeMenu(); - } - if(this.parentNode) - ref_window.document.body.removeChild(this); - }; + this.root.appendChild(element); + if(!disabled) + element.addEventListener("click", inner_onclick); + if(options.autoopen) + element.addEventListener("mouseenter", inner_over); - return root; + 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(".graphcontextmenu"); - if(!elements.length) return; + 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].parentNode) + { + if(result[i].close) + result[i].close(); + else if(result[i].parentNode) result[i].parentNode.removeChild( result[i] ); + } } LiteGraph.extendClass = function ( target, origin ) diff --git a/src/nodes/audio.js b/src/nodes/audio.js index b4fb99307..48ba8b09f 100644 --- a/src/nodes/audio.js +++ b/src/nodes/audio.js @@ -23,8 +23,8 @@ LGAudio.getAudioContext = function() } //in case it crashes - if(this._audio_context.state == "suspended") - this._audio_context.resume(); + //if(this._audio_context.state == "suspended") + // this._audio_context.resume(); return this._audio_context; } @@ -154,8 +154,13 @@ LGAudio.onConnectionsChange = function( connection, slot, connected, link_info ) //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; @@ -171,7 +176,7 @@ LGAudio.createAudioNodeWrapper = function( class_object ) 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 ) @@ -183,6 +188,9 @@ LGAudio.loadSound = function( url, on_complete, on_error ) return; } + if( LGAudio.onProcessAudioURL ) + url = LGAudio.onProcessAudioURL( url ); + //load new sample var request = new XMLHttpRequest(); request.open('GET', url, true); @@ -226,8 +234,9 @@ function LGAudioSource() }; this._loading_audio = false; - this._audio_buffer = null; + 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" ); @@ -235,7 +244,7 @@ function LGAudioSource() //init context var context = LGAudio.getAudioContext(); - //create gain node + //create gain node to control volume this.audionode = context.createGain(); this.audionode.graphnode = this; this.audionode.gain.value = this.properties.gain; @@ -245,6 +254,7 @@ function LGAudioSource() this.loadSound( this.properties.src ); } +LGAudioSource["@src"] = { widget: "resource" }; LGAudioSource.supported_extensions = ["wav","ogg","mp3"]; @@ -256,11 +266,11 @@ LGAudioSource.prototype.onAdded = function(graph) LGAudioSource.prototype.onStart = function() { - if(!this._audio_buffer) + if(!this._audiobuffer) return; if(this.properties.autoplay) - this.playBuffer( this._audio_buffer ); + this.playBuffer( this._audiobuffer ); } LGAudioSource.prototype.onStop = function() @@ -268,6 +278,18 @@ 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(); @@ -280,38 +302,62 @@ LGAudioSource.prototype.stopAllSounds = function() //iterate and stop for(var i = 0; i < this._audionodes.length; ++i ) { - this._audionodes[i].stop(); + 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) - return; + 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; + } + } - for(var i = 0; i < this.inputs.length; ++i) - { - var input = this.inputs[i]; - if(!input.link) - 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; - } + 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._audio_buffer) + if(this._audiobuffer) { if(event == "Play") - this.playBuffer(this._audio_buffer); + this.playBuffer(this._audiobuffer); else if(event == "Stop") this.stopAllSounds(); } @@ -323,6 +369,11 @@ LGAudioSource.prototype.onPropertyChanged = function( name, value ) 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 ) @@ -332,6 +383,7 @@ LGAudioSource.prototype.playBuffer = function( buffer ) //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; @@ -350,7 +402,11 @@ LGAudioSource.prototype.playBuffer = function( buffer ) that._audionodes.splice(index,1); } - audionode.start(); + if(!audionode.started) + { + audionode.started = true; + audionode.start(); + } return audionode; } @@ -365,7 +421,7 @@ LGAudioSource.prototype.loadSound = function( url ) this._request = null; } - this._audio_buffer = null; + this._audiobuffer = null; //points to the audiobuffer once the audio is loaded this._loading_audio = false; if(!url) @@ -379,7 +435,7 @@ LGAudioSource.prototype.loadSound = function( url ) function inner( buffer ) { this.boxcolor = LiteGraph.NODE_DEFAULT_BOXCOLOR; - that._audio_buffer = buffer; + that._audiobuffer = buffer; that._loading_audio = false; //if is playing, then play it if(that.graph && that.graph.status === LGraph.STATUS_RUNNING) @@ -397,7 +453,7 @@ LGAudioSource.prototype.onGetInputs = function() LGAudioSource.prototype.onGetOutputs = function() { - return [["ended",LiteGraph.EVENT]]; + return [["buffer","audiobuffer"],["ended",LiteGraph.EVENT]]; } LGAudioSource.prototype.onDropFile = function(file) @@ -438,7 +494,7 @@ function LGAudioAnalyser() this.addInput("in","audio"); this.addOutput("freqs","array"); - //this.addOutput("time","freq"); + this.addOutput("samples","array"); this._freq_bin = null; this._time_bin = null; @@ -463,14 +519,21 @@ LGAudioAnalyser.prototype.onExecute = function() //send analyzer if(this.isOutputConnected(1)) - this.setOutputData(1,this.audionode); + { + //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) + if(input.link == null) continue; var v = this.getInputData(i); if (v !== undefined) @@ -488,12 +551,11 @@ LGAudioAnalyser.prototype.onGetInputs = function() return [["minDecibels","number"],["maxDecibels","number"],["smoothingTimeConstant","number"]]; } -/* LGAudioAnalyser.prototype.onGetOutputs = function() { - return [["Analyzer","analyzer"]]; + return [["freqs","array"],["samples","array"]]; } -*/ + LGAudioAnalyser.title = "Analyser"; LGAudioAnalyser.desc = "Audio Analyser"; @@ -634,7 +696,7 @@ LGAudioDynamicsCompressor.prototype.onExecute = function() for(var i = 1; i < this.inputs.length; ++i) { var input = this.inputs[i]; - if(!input.link) + if(input.link == null) continue; var v = this.getInputData(i); if(v !== undefined) @@ -721,6 +783,15 @@ LGAudioMixer.prototype.getAudioNodeInInputSlot = function( slot ) 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) @@ -730,7 +801,7 @@ LGAudioMixer.prototype.onExecute = function() { var input = this.inputs[i]; - if(!input.link || input.type == "audio") + if(input.link == null || input.type == "audio") continue; var v = this.getInputData(i); @@ -805,7 +876,7 @@ LGAudioBiquadFilter.prototype.onExecute = function() for(var i = 1; i < this.inputs.length; ++i) { var input = this.inputs[i]; - if(!input.link) + if(input.link == null) continue; var v = this.getInputData(i); if(v !== undefined) @@ -825,6 +896,81 @@ 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 @@ -837,7 +983,7 @@ function LGAudioVisualization() mark: -1 }; - this.addInput("freqs","array"); + this.addInput("data","array"); this.addInput("mark","number"); this.size = [300,200]; this._last_buffer = null; @@ -962,6 +1108,137 @@ 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() { diff --git a/src/nodes/base.js b/src/nodes/base.js index d934e77b9..4bf6d781b 100755 --- a/src/nodes/base.js +++ b/src/nodes/base.js @@ -389,7 +389,7 @@ Console.prototype.onAction = function(action, param) Console.prototype.onExecute = function() { - var msg = this.getInputData(0); + var msg = this.getInputData(1); if(msg !== null) this.properties.msg = msg; console.log(msg); @@ -403,4 +403,62 @@ Console.prototype.onGetInputs = function() 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 ); + + + })(); \ No newline at end of file diff --git a/src/nodes/gltextures.js b/src/nodes/gltextures.js index e86103bb8..0e58df322 100755 --- a/src/nodes/gltextures.js +++ b/src/nodes/gltextures.js @@ -306,17 +306,19 @@ if(typeof(LiteGraph) != "undefined") 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) + if(!ctx.webgl && !LGraphTexturePreview.allow_preview) return; //not working well var tex = this.getInputData(0); - if(!tex) return; + if(!tex) + return; var tex_canvas = null; @@ -353,12 +355,19 @@ if(typeof(LiteGraph) != "undefined") LGraphTextureSave.prototype.onExecute = function() { var tex = this.getInputData(0); - if(!tex) return; + if(!tex) + return; if(this.properties.name) { - var container = LGraphTexture.getTexturesContainer(); - container[ this.properties.name ] = tex; + //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); @@ -564,7 +573,7 @@ if(typeof(LiteGraph) != "undefined") this.addOutput("Texture","Texture"); this.properties = {code:"", width: 512, height: 512}; - this.properties.code = "\nvoid main() {\n vec2 uv = coord;\n vec3 color = vec3(0.0);\n//your code here\n\ngl_FragColor = vec4(color, 1.0);\n}\n"; + 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"; } LGraphTextureShader.title = "Shader"; @@ -574,65 +583,133 @@ if(typeof(LiteGraph) != "undefined") "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 - //replug - if(this._shader_code != this.properties.code) - { - 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; - } - else - this.boxcolor = "green"; - /* - var uniforms = this._shader.uniformLocations; - //disconnect inputs - if(this.inputs) - for(var i = 0; i < this.inputs.length; i++) - { - var slot = this.inputs[i]; - if(slot.link != null) - this.disconnectInput(i); - } + var shader = this.getShader(); + if(!shader) + return; - for(var i = 0; i < uniforms.length; i++) + var tex_slot = 0; + + //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) { - var type = "number"; - if( this._shader.isSampler[i] ) - type = "texture"; - else - { - var v = gl.getUniform(this._shader.program, i); - type = typeof(v); - if(type == "object" && v.length) - { - switch(v.length) - { - 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; - } - } - } - this.addInput(i,type); + data.bind(slot); + data = slot; + slot++; } - */ + shader.setUniform( info.name, data ); } + //render to texture if(!this._tex || this._tex.width != this.properties.width || this._tex.height != this.properties.height ) this._tex = new GL.Texture( this.properties.width, this.properties.height, { format: gl.RGBA, filter: gl.LINEAR }); var tex = this._tex; - var shader = this._shader; var time = this.graph.getTime(); tex.drawTo(function() { shader.uniforms({texSize: [tex.width, tex.height], time: time}).draw( Mesh.getScreenQuad() ); @@ -830,7 +907,7 @@ if(typeof(LiteGraph) != "undefined") \n\ void main() {\n\ vec2 uv = v_coord;\n\ - uv += texture2D(u_textureB, uv).rg * u_factor;\n\ + uv += ( texture2D(u_textureB, uv).rg - vec2(0.5)) * u_factor;\n\ gl_FragColor = texture2D(u_texture, uv);\n\ }\n\ "; @@ -1046,16 +1123,127 @@ if(typeof(LiteGraph) != "undefined") 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("","Texture"); - this.properties = { low_precision: false }; + this.properties = { mipmap_offset: 0, low_precision: false }; + + this._uniforms = { u_texture: 0, u_mipmap_offset: this.properties.mipmap_offset }; } LGraphTextureAverage.title = "Average"; - LGraphTextureAverage.desc = "Compute the total average of a texture and stores it as a 1x1 pixel texture"; + LGraphTextureAverage.desc = "Compute a partial average (32 random samples) of a texture and stores it as a 1x1 pixel texture"; LGraphTextureAverage.prototype.onExecute = function() { @@ -1068,7 +1256,8 @@ if(typeof(LiteGraph) != "undefined") if(!LGraphTextureAverage._shader) { - LGraphTextureAverage._shader = new GL.Shader(Shader.SCREEN_VERTEX_SHADER, LGraphTextureAverage.pixel_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(); @@ -1081,8 +1270,10 @@ if(typeof(LiteGraph) != "undefined") 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,{u_texture:0}); + tex.toViewport( shader, uniforms ); }); this.setOutputData(0,this._temp_texture); @@ -1093,6 +1284,7 @@ if(typeof(LiteGraph) != "undefined") 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\ @@ -1100,8 +1292,8 @@ if(typeof(LiteGraph) != "undefined") 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] ) );\n\ - color += texture2D(u_texture, vec2( 1.0 - u_samples_a[i][j], u_samples_b[i][j] ) );\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\ @@ -2193,6 +2385,8 @@ LGraphTextureKuwaharaFilter.pixel_shader = "\n\ this.size = [LGraphTexture.image_preview_size, LGraphTexture.image_preview_size]; } + LGraphCubemap.title = "Cubemap"; + LGraphCubemap.prototype.onDropFile = function(data, filename, file) { if(!data) diff --git a/src/nodes/midi.js b/src/nodes/midi.js index 405a59767..e790a2b8a 100644 --- a/src/nodes/midi.js +++ b/src/nodes/midi.js @@ -61,6 +61,15 @@ 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() @@ -188,9 +197,18 @@ function MIDIInterface( on_ready, on_error ) 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) @@ -209,8 +227,41 @@ 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 + "'" ); @@ -223,12 +274,15 @@ MIDIInterface.prototype.updatePorts = function() 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) @@ -236,14 +290,17 @@ MIDIInterface.prototype.onMIDIFailure = function(msg) console.error( "Failed to get MIDI access - " + msg ); } -MIDIInterface.prototype.openInputPort = function( port, callback) +MIDIInterface.prototype.openInputPort = function( port, callback ) { - var input_port = this.input_ports.get( port ); + 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) @@ -258,15 +315,27 @@ 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(port); + 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 @@ -308,7 +377,7 @@ LGMIDIIn.prototype.getPropertyInfo = function(name) var values = {}; for (var i = 0; i < this._midi.input_ports.size; ++i) { - var input = this._midi.input_ports.get(i); + var input = this._midi.input_ports.get( "input-" + i); values[i] = i + ".- " + input.name + " version:" + input.version; } return { type: "enum", values: values }; @@ -351,6 +420,7 @@ LGMIDIIn.prototype.onExecute = function() var v = null; switch (output.name) { + case "midi": v = this._midi; break; case "last_midi": v = last; break; default: continue; @@ -565,8 +635,10 @@ LGMIDIEvent.prototype.onExecute = function() v.channel = props.channel; break; case "command": v = props.cmd; 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 "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: @@ -592,6 +664,8 @@ LGMIDIEvent.prototype.onGetOutputs = function() { ["command","number"], ["note","number"], ["velocity","number"], + ["cc","number"], + ["cc_value","number"], ["pitch","number"], ["pitchbend","number"] ]; @@ -601,6 +675,31 @@ LGMIDIEvent.prototype.onGetOutputs = function() { 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() }