diff --git a/README.md b/README.md
index e29bcceb2..cec7f5bf9 100755
--- a/README.md
+++ b/README.md
@@ -9,16 +9,16 @@ Try it in the [demo site](http://tamats.com/projects/litegraph/demo).

## Features
-- Renders on Canvas2D (zoom in, zoom out, panning)
+- Renders on Canvas2D (zoom in, zoom out, panning, can be used inside a WebGLTexture)
- Easy to use editor (searchbox, keyboard shortcuts, multiple selection, context menu, ...)
- Optimized to support hundreds of nodes per graph (on editor but also on execution)
- Customizable theme (colors, shapes, background)
-- Callbacks to personalize every action/drawing/event
+- Callbacks to personalize every action/drawing/event of nodes
- Subgraphs (nodes that contain graphs themselves)
-- Live mode system (hides the graph but calls nodes to render whatever they want, useful to create UI)
+- Live mode system (hides the graph but calls nodes to render whatever they want, useful to create UIs)
- Graphs can be executed in NodeJS
- Highly customizable nodes (color, shape, slots vertical or horizontal, widgets, custom rendering)
-- Easy to integrate in any application
+- Easy to integrate in any JS application
## Nodes provided
Although it is easy to create new node types, LiteGraph comes with some default nodes that could be useful for many cases:
diff --git a/build/litegraph.js b/build/litegraph.js
index 37c27b8e0..aa3527197 100644
--- a/build/litegraph.js
+++ b/build/litegraph.js
@@ -8485,179 +8485,179 @@ LiteGraph.registerNodeType("basic/script", NodeScript );
})(this);
-//event related nodes
-(function(global){
-var LiteGraph = global.LiteGraph;
-
-//Show value inside the debug console
-function LogEvent()
-{
- this.size = [60,20];
- this.addInput("event", LiteGraph.ACTION);
-}
-
-LogEvent.title = "Log Event";
-LogEvent.desc = "Log event in console";
-
-LogEvent.prototype.onAction = function( action, param )
-{
- console.log( action, param );
-}
-
-LiteGraph.registerNodeType("events/log", LogEvent );
-
-
-//Filter events
-function FilterEvent()
-{
- this.size = [60,20];
- this.addInput("event", LiteGraph.ACTION);
- this.addOutput("event", LiteGraph.EVENT);
- this.properties = {
- equal_to: "",
- has_property:"",
- property_equal_to: ""
- };
-}
-
-FilterEvent.title = "Filter Event";
-FilterEvent.desc = "Blocks events that do not match the filter";
-
-FilterEvent.prototype.onAction = function( action, param )
-{
- if( param == null )
- return;
-
- if( this.properties.equal_to && this.properties.equal_to != param )
- return;
-
- if( this.properties.has_property )
- {
- var prop = param[ this.properties.has_property ];
- if( prop == null )
- return;
-
- if( this.properties.property_equal_to && this.properties.property_equal_to != prop )
- return;
- }
-
- this.triggerSlot(0,param);
-}
-
-LiteGraph.registerNodeType("events/filter", FilterEvent );
-
-//Show value inside the debug console
-function DelayEvent()
-{
- this.size = [60,20];
- this.addProperty( "time", 1000 );
- this.addInput("event", LiteGraph.ACTION);
- this.addOutput("on_time", LiteGraph.EVENT);
-
- this._pending = [];
-}
-
-DelayEvent.title = "Delay";
-DelayEvent.desc = "Delays one event";
-
-DelayEvent.prototype.onAction = function(action, param)
-{
- this._pending.push([ this.properties.time, param ]);
-}
-
-DelayEvent.prototype.onExecute = function()
-{
- var dt = this.graph.elapsed_time * 1000; //in ms
-
- for(var i = 0; i < this._pending.length; ++i)
- {
- var action = this._pending[i];
- action[0] -= dt;
- if( action[0] > 0 )
- continue;
-
- //remove
- this._pending.splice(i,1);
- --i;
-
- //trigger
- this.trigger(null, action[1]);
- }
-}
-
-DelayEvent.prototype.onGetInputs = function()
-{
- return [["event",LiteGraph.ACTION]];
-}
-
-LiteGraph.registerNodeType("events/delay", DelayEvent );
-
-
-//Show value inside the debug console
-function TimerEvent()
-{
- this.addProperty("interval", 1000);
- this.addProperty("event", "tick");
- this.addOutput("on_tick", LiteGraph.EVENT);
- this.time = 0;
- this.last_interval = 1000;
- this.triggered = false;
-}
-
-TimerEvent.title = "Timer";
-TimerEvent.desc = "Sends an event every N milliseconds";
-
-TimerEvent.prototype.onStart = function()
-{
- this.time = 0;
-}
-
-TimerEvent.prototype.getTitle = function()
-{
- return "Timer: " + this.last_interval.toString() + "ms";
-}
-
-TimerEvent.on_color = "#AAA";
-TimerEvent.off_color = "#222";
-
-TimerEvent.prototype.onDrawBackground = function()
-{
- this.boxcolor = this.triggered ? TimerEvent.on_color : TimerEvent.off_color;
- this.triggered = false;
-}
-
-TimerEvent.prototype.onExecute = function()
-{
- var dt = this.graph.elapsed_time * 1000; //in ms
- this.time += dt;
- this.last_interval = Math.max(1, this.getInputOrProperty("interval") | 0);
-
- if( this.time < this.last_interval || isNaN(this.last_interval) )
- {
- if( this.inputs && this.inputs.length > 1 && this.inputs[1] )
- this.setOutputData(1,false);
- return;
- }
- this.triggered = true;
- this.time = this.time % this.last_interval;
- this.trigger( "on_tick", this.properties.event );
- if( this.inputs && this.inputs.length > 1 && this.inputs[1] )
- this.setOutputData( 1, true );
-}
-
-TimerEvent.prototype.onGetInputs = function()
-{
- return [["interval","number"]];
-}
-
-TimerEvent.prototype.onGetOutputs = function()
-{
- return [["tick","boolean"]];
-}
-
-LiteGraph.registerNodeType("events/timer", TimerEvent );
-
-
+//event related nodes
+(function(global){
+var LiteGraph = global.LiteGraph;
+
+//Show value inside the debug console
+function LogEvent()
+{
+ this.size = [60,20];
+ this.addInput("event", LiteGraph.ACTION);
+}
+
+LogEvent.title = "Log Event";
+LogEvent.desc = "Log event in console";
+
+LogEvent.prototype.onAction = function( action, param )
+{
+ console.log( action, param );
+}
+
+LiteGraph.registerNodeType("events/log", LogEvent );
+
+
+//Filter events
+function FilterEvent()
+{
+ this.size = [60,20];
+ this.addInput("event", LiteGraph.ACTION);
+ this.addOutput("event", LiteGraph.EVENT);
+ this.properties = {
+ equal_to: "",
+ has_property:"",
+ property_equal_to: ""
+ };
+}
+
+FilterEvent.title = "Filter Event";
+FilterEvent.desc = "Blocks events that do not match the filter";
+
+FilterEvent.prototype.onAction = function( action, param )
+{
+ if( param == null )
+ return;
+
+ if( this.properties.equal_to && this.properties.equal_to != param )
+ return;
+
+ if( this.properties.has_property )
+ {
+ var prop = param[ this.properties.has_property ];
+ if( prop == null )
+ return;
+
+ if( this.properties.property_equal_to && this.properties.property_equal_to != prop )
+ return;
+ }
+
+ this.triggerSlot(0,param);
+}
+
+LiteGraph.registerNodeType("events/filter", FilterEvent );
+
+//Show value inside the debug console
+function DelayEvent()
+{
+ this.size = [60,20];
+ this.addProperty( "time", 1000 );
+ this.addInput("event", LiteGraph.ACTION);
+ this.addOutput("on_time", LiteGraph.EVENT);
+
+ this._pending = [];
+}
+
+DelayEvent.title = "Delay";
+DelayEvent.desc = "Delays one event";
+
+DelayEvent.prototype.onAction = function(action, param)
+{
+ this._pending.push([ this.properties.time, param ]);
+}
+
+DelayEvent.prototype.onExecute = function()
+{
+ var dt = this.graph.elapsed_time * 1000; //in ms
+
+ for(var i = 0; i < this._pending.length; ++i)
+ {
+ var action = this._pending[i];
+ action[0] -= dt;
+ if( action[0] > 0 )
+ continue;
+
+ //remove
+ this._pending.splice(i,1);
+ --i;
+
+ //trigger
+ this.trigger(null, action[1]);
+ }
+}
+
+DelayEvent.prototype.onGetInputs = function()
+{
+ return [["event",LiteGraph.ACTION]];
+}
+
+LiteGraph.registerNodeType("events/delay", DelayEvent );
+
+
+//Show value inside the debug console
+function TimerEvent()
+{
+ this.addProperty("interval", 1000);
+ this.addProperty("event", "tick");
+ this.addOutput("on_tick", LiteGraph.EVENT);
+ this.time = 0;
+ this.last_interval = 1000;
+ this.triggered = false;
+}
+
+TimerEvent.title = "Timer";
+TimerEvent.desc = "Sends an event every N milliseconds";
+
+TimerEvent.prototype.onStart = function()
+{
+ this.time = 0;
+}
+
+TimerEvent.prototype.getTitle = function()
+{
+ return "Timer: " + this.last_interval.toString() + "ms";
+}
+
+TimerEvent.on_color = "#AAA";
+TimerEvent.off_color = "#222";
+
+TimerEvent.prototype.onDrawBackground = function()
+{
+ this.boxcolor = this.triggered ? TimerEvent.on_color : TimerEvent.off_color;
+ this.triggered = false;
+}
+
+TimerEvent.prototype.onExecute = function()
+{
+ var dt = this.graph.elapsed_time * 1000; //in ms
+ this.time += dt;
+ this.last_interval = Math.max(1, this.getInputOrProperty("interval") | 0);
+
+ if( this.time < this.last_interval || isNaN(this.last_interval) )
+ {
+ if( this.inputs && this.inputs.length > 1 && this.inputs[1] )
+ this.setOutputData(1,false);
+ return;
+ }
+ this.triggered = true;
+ this.time = this.time % this.last_interval;
+ this.trigger( "on_tick", this.properties.event );
+ if( this.inputs && this.inputs.length > 1 && this.inputs[1] )
+ this.setOutputData( 1, true );
+}
+
+TimerEvent.prototype.onGetInputs = function()
+{
+ return [["interval","number"]];
+}
+
+TimerEvent.prototype.onGetOutputs = function()
+{
+ return [["tick","boolean"]];
+}
+
+LiteGraph.registerNodeType("events/timer", TimerEvent );
+
+
})(this);
//widgets
(function(global){
@@ -9385,1296 +9385,1924 @@ var LiteGraph = global.LiteGraph;
LiteGraph.registerNodeType("widget/panel", WidgetPanel );
})(this);
-(function(global){
-var LiteGraph = global.LiteGraph;
-
-function GamepadInput()
-{
- this.addOutput("left_x_axis","number");
- this.addOutput("left_y_axis","number");
- this.addOutput( "button_pressed", LiteGraph.EVENT );
- this.properties = { gamepad_index: 0, threshold: 0.1 };
-
- this._left_axis = new Float32Array(2);
- this._right_axis = new Float32Array(2);
- this._triggers = new Float32Array(2);
- this._previous_buttons = new Uint8Array(17);
- this._current_buttons = new Uint8Array(17);
-}
-
-GamepadInput.title = "Gamepad";
-GamepadInput.desc = "gets the input of the gamepad";
-
-GamepadInput.zero = new Float32Array(2);
-GamepadInput.buttons = ["a","b","x","y","lb","rb","lt","rt","back","start","ls","rs","home"];
-
-GamepadInput.prototype.onExecute = function()
-{
- //get gamepad
- var gamepad = this.getGamepad();
- var threshold = this.properties.threshold || 0.0;
-
- if(gamepad)
- {
- this._left_axis[0] = Math.abs( gamepad.xbox.axes["lx"] ) > threshold ? gamepad.xbox.axes["lx"] : 0;
- this._left_axis[1] = Math.abs( gamepad.xbox.axes["ly"] ) > threshold ? gamepad.xbox.axes["ly"] : 0;
- this._right_axis[0] = Math.abs( gamepad.xbox.axes["rx"] ) > threshold ? gamepad.xbox.axes["rx"] : 0;
- this._right_axis[1] = Math.abs( gamepad.xbox.axes["ry"] ) > threshold ? gamepad.xbox.axes["ry"] : 0;
- this._triggers[0] = Math.abs( gamepad.xbox.axes["ltrigger"] ) > threshold ? gamepad.xbox.axes["ltrigger"] : 0;
- this._triggers[1] = Math.abs( gamepad.xbox.axes["rtrigger"] ) > threshold ? gamepad.xbox.axes["rtrigger"] : 0;
- }
-
- if(this.outputs)
- {
- for(var i = 0; i < this.outputs.length; i++)
- {
- var output = this.outputs[i];
- if(!output.links || !output.links.length)
- continue;
- var v = null;
-
- if(gamepad)
- {
- switch( output.name )
- {
- case "left_axis": v = this._left_axis; break;
- case "right_axis": v = this._right_axis; break;
- case "left_x_axis": v = this._left_axis[0]; break;
- case "left_y_axis": v = this._left_axis[1]; break;
- case "right_x_axis": v = this._right_axis[0]; break;
- case "right_y_axis": v = this._right_axis[1]; break;
- case "trigger_left": v = this._triggers[0]; break;
- case "trigger_right": v = this._triggers[1]; break;
- case "a_button": v = gamepad.xbox.buttons["a"] ? 1 : 0; break;
- case "b_button": v = gamepad.xbox.buttons["b"] ? 1 : 0; break;
- case "x_button": v = gamepad.xbox.buttons["x"] ? 1 : 0; break;
- case "y_button": v = gamepad.xbox.buttons["y"] ? 1 : 0; break;
- case "lb_button": v = gamepad.xbox.buttons["lb"] ? 1 : 0; break;
- case "rb_button": v = gamepad.xbox.buttons["rb"] ? 1 : 0; break;
- case "ls_button": v = gamepad.xbox.buttons["ls"] ? 1 : 0; break;
- case "rs_button": v = gamepad.xbox.buttons["rs"] ? 1 : 0; break;
- case "start_button": v = gamepad.xbox.buttons["start"] ? 1 : 0; break;
- case "back_button": v = gamepad.xbox.buttons["back"] ? 1 : 0; break;
- case "button_pressed":
- for(var j = 0; j < this._current_buttons.length; ++j)
- {
- if( this._current_buttons[j] && !this._previous_buttons[j] )
- this.triggerSlot( i, GamepadInput.buttons[j] );
- }
- break;
- default: break;
- }
- }
- else
- {
- //if no gamepad is connected, output 0
- switch( output.name )
- {
- case "button_pressed": break;
- case "left_axis":
- case "right_axis":
- v = GamepadInput.zero;
- break;
- default:
- v = 0;
- }
- }
- this.setOutputData(i,v);
- }
- }
-}
-
-GamepadInput.prototype.getGamepad = function()
-{
- var getGamepads = navigator.getGamepads || navigator.webkitGetGamepads || navigator.mozGetGamepads;
- if(!getGamepads)
- return null;
- var gamepads = getGamepads.call(navigator);
- var gamepad = null;
-
- this._previous_buttons.set( this._current_buttons );
-
- //pick the first connected
- for(var i = this.properties.gamepad_index; i < 4; i++)
- {
- if (gamepads[i])
- {
- gamepad = gamepads[i];
-
- //xbox controller mapping
- var xbox = this.xbox_mapping;
- if(!xbox)
- xbox = this.xbox_mapping = { axes:[], buttons:{}, hat: ""};
-
- xbox.axes["lx"] = gamepad.axes[0];
- xbox.axes["ly"] = gamepad.axes[1];
- xbox.axes["rx"] = gamepad.axes[2];
- xbox.axes["ry"] = gamepad.axes[3];
- xbox.axes["ltrigger"] = gamepad.buttons[6].value;
- xbox.axes["rtrigger"] = gamepad.buttons[7].value;
-
- for(var j = 0; j < gamepad.buttons.length; j++)
- {
- this._current_buttons[j] = gamepad.buttons[j].pressed;
-
- //mapping of XBOX
- switch(j) //I use a switch to ensure that a player with another gamepad could play
- {
- case 0: xbox.buttons["a"] = gamepad.buttons[j].pressed; break;
- case 1: xbox.buttons["b"] = gamepad.buttons[j].pressed; break;
- case 2: xbox.buttons["x"] = gamepad.buttons[j].pressed; break;
- case 3: xbox.buttons["y"] = gamepad.buttons[j].pressed; break;
- case 4: xbox.buttons["lb"] = gamepad.buttons[j].pressed; break;
- case 5: xbox.buttons["rb"] = gamepad.buttons[j].pressed; break;
- case 6: xbox.buttons["lt"] = gamepad.buttons[j].pressed; break;
- case 7: xbox.buttons["rt"] = gamepad.buttons[j].pressed; break;
- case 8: xbox.buttons["back"] = gamepad.buttons[j].pressed; break;
- case 9: xbox.buttons["start"] = gamepad.buttons[j].pressed; break;
- case 10: xbox.buttons["ls"] = gamepad.buttons[j].pressed; break;
- case 11: xbox.buttons["rs"] = gamepad.buttons[j].pressed; break;
- case 12: if( gamepad.buttons[j].pressed) xbox.hat += "up"; break;
- case 13: if( gamepad.buttons[j].pressed) xbox.hat += "down"; break;
- case 14: if( gamepad.buttons[j].pressed) xbox.hat += "left"; break;
- case 15: if( gamepad.buttons[j].pressed) xbox.hat += "right"; break;
- case 16: xbox.buttons["home"] = gamepad.buttons[j].pressed; break;
- default:
- }
- }
- gamepad.xbox = xbox;
- return gamepad;
- }
- }
-}
-
-GamepadInput.prototype.onDrawBackground = function(ctx)
-{
- if(this.flags.collapsed)
- return;
-
- //render gamepad state?
- var la = this._left_axis;
- var ra = this._right_axis;
- ctx.strokeStyle = "#88A";
- ctx.strokeRect( (la[0] + 1) * 0.5 * this.size[0] - 4, (la[1] + 1) * 0.5 * this.size[1] - 4, 8, 8 );
- ctx.strokeStyle = "#8A8";
- ctx.strokeRect( (ra[0] + 1) * 0.5 * this.size[0] - 4, (ra[1] + 1) * 0.5 * this.size[1] - 4, 8, 8 );
- var h = this.size[1] / this._current_buttons.length
- ctx.fillStyle = "#AEB";
- for(var i = 0; i < this._current_buttons.length; ++i)
- if(this._current_buttons[i])
- ctx.fillRect( 0, h * i, 6, h);
-}
-
-GamepadInput.prototype.onGetOutputs = function() {
- return [
- ["left_axis","vec2"],
- ["right_axis","vec2"],
- ["left_x_axis","number"],
- ["left_y_axis","number"],
- ["right_x_axis","number"],
- ["right_y_axis","number"],
- ["trigger_left","number"],
- ["trigger_right","number"],
- ["a_button","number"],
- ["b_button","number"],
- ["x_button","number"],
- ["y_button","number"],
- ["lb_button","number"],
- ["rb_button","number"],
- ["ls_button","number"],
- ["rs_button","number"],
- ["start","number"],
- ["back","number"],
- ["button_pressed", LiteGraph.EVENT]
- ];
-}
-
-LiteGraph.registerNodeType("input/gamepad", GamepadInput );
-
+(function(global){
+var LiteGraph = global.LiteGraph;
+
+function GamepadInput()
+{
+ this.addOutput("left_x_axis","number");
+ this.addOutput("left_y_axis","number");
+ this.addOutput( "button_pressed", LiteGraph.EVENT );
+ this.properties = { gamepad_index: 0, threshold: 0.1 };
+
+ this._left_axis = new Float32Array(2);
+ this._right_axis = new Float32Array(2);
+ this._triggers = new Float32Array(2);
+ this._previous_buttons = new Uint8Array(17);
+ this._current_buttons = new Uint8Array(17);
+}
+
+GamepadInput.title = "Gamepad";
+GamepadInput.desc = "gets the input of the gamepad";
+
+GamepadInput.zero = new Float32Array(2);
+GamepadInput.buttons = ["a","b","x","y","lb","rb","lt","rt","back","start","ls","rs","home"];
+
+GamepadInput.prototype.onExecute = function()
+{
+ //get gamepad
+ var gamepad = this.getGamepad();
+ var threshold = this.properties.threshold || 0.0;
+
+ if(gamepad)
+ {
+ this._left_axis[0] = Math.abs( gamepad.xbox.axes["lx"] ) > threshold ? gamepad.xbox.axes["lx"] : 0;
+ this._left_axis[1] = Math.abs( gamepad.xbox.axes["ly"] ) > threshold ? gamepad.xbox.axes["ly"] : 0;
+ this._right_axis[0] = Math.abs( gamepad.xbox.axes["rx"] ) > threshold ? gamepad.xbox.axes["rx"] : 0;
+ this._right_axis[1] = Math.abs( gamepad.xbox.axes["ry"] ) > threshold ? gamepad.xbox.axes["ry"] : 0;
+ this._triggers[0] = Math.abs( gamepad.xbox.axes["ltrigger"] ) > threshold ? gamepad.xbox.axes["ltrigger"] : 0;
+ this._triggers[1] = Math.abs( gamepad.xbox.axes["rtrigger"] ) > threshold ? gamepad.xbox.axes["rtrigger"] : 0;
+ }
+
+ if(this.outputs)
+ {
+ for(var i = 0; i < this.outputs.length; i++)
+ {
+ var output = this.outputs[i];
+ if(!output.links || !output.links.length)
+ continue;
+ var v = null;
+
+ if(gamepad)
+ {
+ switch( output.name )
+ {
+ case "left_axis": v = this._left_axis; break;
+ case "right_axis": v = this._right_axis; break;
+ case "left_x_axis": v = this._left_axis[0]; break;
+ case "left_y_axis": v = this._left_axis[1]; break;
+ case "right_x_axis": v = this._right_axis[0]; break;
+ case "right_y_axis": v = this._right_axis[1]; break;
+ case "trigger_left": v = this._triggers[0]; break;
+ case "trigger_right": v = this._triggers[1]; break;
+ case "a_button": v = gamepad.xbox.buttons["a"] ? 1 : 0; break;
+ case "b_button": v = gamepad.xbox.buttons["b"] ? 1 : 0; break;
+ case "x_button": v = gamepad.xbox.buttons["x"] ? 1 : 0; break;
+ case "y_button": v = gamepad.xbox.buttons["y"] ? 1 : 0; break;
+ case "lb_button": v = gamepad.xbox.buttons["lb"] ? 1 : 0; break;
+ case "rb_button": v = gamepad.xbox.buttons["rb"] ? 1 : 0; break;
+ case "ls_button": v = gamepad.xbox.buttons["ls"] ? 1 : 0; break;
+ case "rs_button": v = gamepad.xbox.buttons["rs"] ? 1 : 0; break;
+ case "start_button": v = gamepad.xbox.buttons["start"] ? 1 : 0; break;
+ case "back_button": v = gamepad.xbox.buttons["back"] ? 1 : 0; break;
+ case "button_pressed":
+ for(var j = 0; j < this._current_buttons.length; ++j)
+ {
+ if( this._current_buttons[j] && !this._previous_buttons[j] )
+ this.triggerSlot( i, GamepadInput.buttons[j] );
+ }
+ break;
+ default: break;
+ }
+ }
+ else
+ {
+ //if no gamepad is connected, output 0
+ switch( output.name )
+ {
+ case "button_pressed": break;
+ case "left_axis":
+ case "right_axis":
+ v = GamepadInput.zero;
+ break;
+ default:
+ v = 0;
+ }
+ }
+ this.setOutputData(i,v);
+ }
+ }
+}
+
+GamepadInput.prototype.getGamepad = function()
+{
+ var getGamepads = navigator.getGamepads || navigator.webkitGetGamepads || navigator.mozGetGamepads;
+ if(!getGamepads)
+ return null;
+ var gamepads = getGamepads.call(navigator);
+ var gamepad = null;
+
+ this._previous_buttons.set( this._current_buttons );
+
+ //pick the first connected
+ for(var i = this.properties.gamepad_index; i < 4; i++)
+ {
+ if (gamepads[i])
+ {
+ gamepad = gamepads[i];
+
+ //xbox controller mapping
+ var xbox = this.xbox_mapping;
+ if(!xbox)
+ xbox = this.xbox_mapping = { axes:[], buttons:{}, hat: ""};
+
+ xbox.axes["lx"] = gamepad.axes[0];
+ xbox.axes["ly"] = gamepad.axes[1];
+ xbox.axes["rx"] = gamepad.axes[2];
+ xbox.axes["ry"] = gamepad.axes[3];
+ xbox.axes["ltrigger"] = gamepad.buttons[6].value;
+ xbox.axes["rtrigger"] = gamepad.buttons[7].value;
+
+ for(var j = 0; j < gamepad.buttons.length; j++)
+ {
+ this._current_buttons[j] = gamepad.buttons[j].pressed;
+
+ //mapping of XBOX
+ switch(j) //I use a switch to ensure that a player with another gamepad could play
+ {
+ case 0: xbox.buttons["a"] = gamepad.buttons[j].pressed; break;
+ case 1: xbox.buttons["b"] = gamepad.buttons[j].pressed; break;
+ case 2: xbox.buttons["x"] = gamepad.buttons[j].pressed; break;
+ case 3: xbox.buttons["y"] = gamepad.buttons[j].pressed; break;
+ case 4: xbox.buttons["lb"] = gamepad.buttons[j].pressed; break;
+ case 5: xbox.buttons["rb"] = gamepad.buttons[j].pressed; break;
+ case 6: xbox.buttons["lt"] = gamepad.buttons[j].pressed; break;
+ case 7: xbox.buttons["rt"] = gamepad.buttons[j].pressed; break;
+ case 8: xbox.buttons["back"] = gamepad.buttons[j].pressed; break;
+ case 9: xbox.buttons["start"] = gamepad.buttons[j].pressed; break;
+ case 10: xbox.buttons["ls"] = gamepad.buttons[j].pressed; break;
+ case 11: xbox.buttons["rs"] = gamepad.buttons[j].pressed; break;
+ case 12: if( gamepad.buttons[j].pressed) xbox.hat += "up"; break;
+ case 13: if( gamepad.buttons[j].pressed) xbox.hat += "down"; break;
+ case 14: if( gamepad.buttons[j].pressed) xbox.hat += "left"; break;
+ case 15: if( gamepad.buttons[j].pressed) xbox.hat += "right"; break;
+ case 16: xbox.buttons["home"] = gamepad.buttons[j].pressed; break;
+ default:
+ }
+ }
+ gamepad.xbox = xbox;
+ return gamepad;
+ }
+ }
+}
+
+GamepadInput.prototype.onDrawBackground = function(ctx)
+{
+ if(this.flags.collapsed)
+ return;
+
+ //render gamepad state?
+ var la = this._left_axis;
+ var ra = this._right_axis;
+ ctx.strokeStyle = "#88A";
+ ctx.strokeRect( (la[0] + 1) * 0.5 * this.size[0] - 4, (la[1] + 1) * 0.5 * this.size[1] - 4, 8, 8 );
+ ctx.strokeStyle = "#8A8";
+ ctx.strokeRect( (ra[0] + 1) * 0.5 * this.size[0] - 4, (ra[1] + 1) * 0.5 * this.size[1] - 4, 8, 8 );
+ var h = this.size[1] / this._current_buttons.length
+ ctx.fillStyle = "#AEB";
+ for(var i = 0; i < this._current_buttons.length; ++i)
+ if(this._current_buttons[i])
+ ctx.fillRect( 0, h * i, 6, h);
+}
+
+GamepadInput.prototype.onGetOutputs = function() {
+ return [
+ ["left_axis","vec2"],
+ ["right_axis","vec2"],
+ ["left_x_axis","number"],
+ ["left_y_axis","number"],
+ ["right_x_axis","number"],
+ ["right_y_axis","number"],
+ ["trigger_left","number"],
+ ["trigger_right","number"],
+ ["a_button","number"],
+ ["b_button","number"],
+ ["x_button","number"],
+ ["y_button","number"],
+ ["lb_button","number"],
+ ["rb_button","number"],
+ ["ls_button","number"],
+ ["rs_button","number"],
+ ["start","number"],
+ ["back","number"],
+ ["button_pressed", LiteGraph.EVENT]
+ ];
+}
+
+LiteGraph.registerNodeType("input/gamepad", GamepadInput );
+
})(this);
-(function(global){
-var LiteGraph = global.LiteGraph;
-
-//Converter
-function Converter()
-{
- this.addInput("in","*");
- this.size = [60,20];
-}
-
-Converter.title = "Converter";
-Converter.desc = "type A to type B";
-
-Converter.prototype.onExecute = function()
-{
- var v = this.getInputData(0);
- if(v == null)
- return;
-
- if(this.outputs)
- for(var i = 0; i < this.outputs.length; i++)
- {
- var output = this.outputs[i];
- if(!output.links || !output.links.length)
- continue;
-
- var result = null;
- switch( output.name )
- {
- case "number": result = v.length ? v[0] : parseFloat(v); break;
- case "vec2":
- case "vec3":
- case "vec4":
- var result = null;
- var count = 1;
- switch(output.name)
- {
- case "vec2": count = 2; break;
- case "vec3": count = 3; break;
- case "vec4": count = 4; break;
- }
-
- var result = new Float32Array( count );
- if( v.length )
- {
- for(var j = 0; j < v.length && j < result.length; j++)
- result[j] = v[j];
- }
- else
- result[0] = parseFloat(v);
- break;
- }
- this.setOutputData(i, result);
- }
-}
-
-Converter.prototype.onGetOutputs = function() {
- return [["number","number"],["vec2","vec2"],["vec3","vec3"],["vec4","vec4"]];
-}
-
-LiteGraph.registerNodeType("math/converter", Converter );
-
-
-//Bypass
-function Bypass()
-{
- this.addInput("in");
- this.addOutput("out");
- this.size = [60,20];
-}
-
-Bypass.title = "Bypass";
-Bypass.desc = "removes the type";
-
-Bypass.prototype.onExecute = function()
-{
- var v = this.getInputData(0);
- this.setOutputData(0, v);
-}
-
-LiteGraph.registerNodeType("math/bypass", Bypass );
-
-
-
-function MathRange()
-{
- this.addInput("in","number",{locked:true});
- this.addOutput("out","number",{locked:true});
-
- this.addProperty( "in", 0 );
- this.addProperty( "in_min", 0 );
- this.addProperty( "in_max", 1 );
- this.addProperty( "out_min", 0 );
- this.addProperty( "out_max", 1 );
-
- this.size = [80,20];
-}
-
-MathRange.title = "Range";
-MathRange.desc = "Convert a number from one range to another";
-
-MathRange.prototype.getTitle = function()
-{
- if(this.flags.collapsed)
- return (this._last_v || 0).toFixed(2);
- return this.title;
-}
-
-MathRange.prototype.onExecute = function()
-{
- if(this.inputs)
- for(var i = 0; i < this.inputs.length; i++)
- {
- var input = this.inputs[i];
- var v = this.getInputData(i);
- if(v === undefined)
- continue;
- this.properties[ input.name ] = v;
- }
-
- var v = this.properties["in"];
- if(v === undefined || v === null || v.constructor !== Number)
- v = 0;
-
- var in_min = this.properties.in_min;
- var in_max = this.properties.in_max;
- var out_min = this.properties.out_min;
- var out_max = this.properties.out_max;
-
- this._last_v = ((v - in_min) / (in_max - in_min)) * (out_max - out_min) + out_min;
- this.setOutputData(0, this._last_v );
-}
-
-MathRange.prototype.onDrawBackground = function(ctx)
-{
- //show the current value
- if(this._last_v)
- this.outputs[0].label = this._last_v.toFixed(3);
- else
- this.outputs[0].label = "?";
-}
-
-MathRange.prototype.onGetInputs = function() {
- return [["in_min","number"],["in_max","number"],["out_min","number"],["out_max","number"]];
-}
-
-LiteGraph.registerNodeType("math/range", MathRange);
-
-
-
-function MathRand()
-{
- this.addOutput("value","number");
- this.addProperty( "min", 0 );
- this.addProperty( "max", 1 );
- this.size = [60,20];
-}
-
-MathRand.title = "Rand";
-MathRand.desc = "Random number";
-
-MathRand.prototype.onExecute = function()
-{
- if(this.inputs)
- for(var i = 0; i < this.inputs.length; i++)
- {
- var input = this.inputs[i];
- var v = this.getInputData(i);
- if(v === undefined)
- continue;
- this.properties[input.name] = v;
- }
-
- var min = this.properties.min;
- var max = this.properties.max;
- this._last_v = Math.random() * (max-min) + min;
- this.setOutputData(0, this._last_v );
-}
-
-MathRand.prototype.onDrawBackground = function(ctx)
-{
- //show the current value
- if(this._last_v)
- this.outputs[0].label = this._last_v.toFixed(3);
- else
- this.outputs[0].label = "?";
-}
-
-MathRand.prototype.onGetInputs = function() {
- return [["min","number"],["max","number"]];
-}
-
-LiteGraph.registerNodeType("math/rand", MathRand);
-
-//Math clamp
-function MathClamp()
-{
- this.addInput("in","number");
- this.addOutput("out","number");
- this.size = [60,20];
- this.addProperty( "min", 0 );
- this.addProperty( "max", 1 );
-}
-
-MathClamp.title = "Clamp";
-MathClamp.desc = "Clamp number between min and max";
-MathClamp.filter = "shader";
-
-MathClamp.prototype.onExecute = function()
-{
- var v = this.getInputData(0);
- if(v == null) return;
- v = Math.max(this.properties.min,v);
- v = Math.min(this.properties.max,v);
- this.setOutputData(0, v );
-}
-
-MathClamp.prototype.getCode = function(lang)
-{
- var code = "";
- if(this.isInputConnected(0))
- code += "clamp({{0}}," + this.properties.min + "," + this.properties.max + ")";
- return code;
-}
-
-LiteGraph.registerNodeType("math/clamp", MathClamp );
-
-
-
-//Math ABS
-function MathLerp()
-{
- this.properties = { f: 0.5 };
- this.addInput("A","number");
- this.addInput("B","number");
-
- this.addOutput("out","number");
-}
-
-MathLerp.title = "Lerp";
-MathLerp.desc = "Linear Interpolation";
-
-MathLerp.prototype.onExecute = function()
-{
- var v1 = this.getInputData(0);
- if(v1 == null)
- v1 = 0;
- var v2 = this.getInputData(1);
- if(v2 == null)
- v2 = 0;
-
- var f = this.properties.f;
-
- var _f = this.getInputData(2);
- if(_f !== undefined)
- f = _f;
-
- this.setOutputData(0, v1 * (1-f) + v2 * f );
-}
-
-MathLerp.prototype.onGetInputs = function()
-{
- return [["f","number"]];
-}
-
-LiteGraph.registerNodeType("math/lerp", MathLerp);
-
-
-
-//Math ABS
-function MathAbs()
-{
- this.addInput("in","number");
- this.addOutput("out","number");
- this.size = [60,20];
-}
-
-MathAbs.title = "Abs";
-MathAbs.desc = "Absolute";
-
-MathAbs.prototype.onExecute = function()
-{
- var v = this.getInputData(0);
- if(v == null) return;
- this.setOutputData(0, Math.abs(v) );
-}
-
-LiteGraph.registerNodeType("math/abs", MathAbs);
-
-
-//Math Floor
-function MathFloor()
-{
- this.addInput("in","number");
- this.addOutput("out","number");
- this.size = [60,20];
-}
-
-MathFloor.title = "Floor";
-MathFloor.desc = "Floor number to remove fractional part";
-
-MathFloor.prototype.onExecute = function()
-{
- var v = this.getInputData(0);
- if(v == null) return;
- this.setOutputData(0, Math.floor(v) );
-}
-
-LiteGraph.registerNodeType("math/floor", MathFloor );
-
-
-//Math frac
-function MathFrac()
-{
- this.addInput("in","number");
- this.addOutput("out","number");
- this.size = [60,20];
-}
-
-MathFrac.title = "Frac";
-MathFrac.desc = "Returns fractional part";
-
-MathFrac.prototype.onExecute = function()
-{
- var v = this.getInputData(0);
- if(v == null)
- return;
- this.setOutputData(0, v%1 );
-}
-
-LiteGraph.registerNodeType("math/frac",MathFrac);
-
-
-//Math Floor
-function MathSmoothStep()
-{
- this.addInput("in","number");
- this.addOutput("out","number");
- this.size = [60,20];
- this.properties = { A: 0, B: 1 };
-}
-
-MathSmoothStep.title = "Smoothstep";
-MathSmoothStep.desc = "Smoothstep";
-
-MathSmoothStep.prototype.onExecute = function()
-{
- var v = this.getInputData(0);
- if(v === undefined)
- return;
-
- var edge0 = this.properties.A;
- var edge1 = this.properties.B;
-
- // Scale, bias and saturate x to 0..1 range
- v = Math.clamp((v - edge0)/(edge1 - edge0), 0.0, 1.0);
- // Evaluate polynomial
- v = v*v*(3 - 2*v);
-
- this.setOutputData(0, v );
-}
-
-LiteGraph.registerNodeType("math/smoothstep", MathSmoothStep );
-
-//Math scale
-function MathScale()
-{
- this.addInput("in","number",{label:""});
- this.addOutput("out","number",{label:""});
- this.size = [60,20];
- this.addProperty( "factor", 1 );
-}
-
-MathScale.title = "Scale";
-MathScale.desc = "v * factor";
-
-MathScale.prototype.onExecute = function()
-{
- var value = this.getInputData(0);
- if(value != null)
- this.setOutputData(0, value * this.properties.factor );
-}
-
-LiteGraph.registerNodeType("math/scale", MathScale );
-
-
-//Math Average
-function MathAverageFilter()
-{
- this.addInput("in","number");
- this.addOutput("out","number");
- this.size = [60,20];
- this.addProperty( "samples", 10 );
- this._values = new Float32Array(10);
- this._current = 0;
-}
-
-MathAverageFilter.title = "Average";
-MathAverageFilter.desc = "Average Filter";
-
-MathAverageFilter.prototype.onExecute = function()
-{
- var v = this.getInputData(0);
- if(v == null)
- v = 0;
-
- var num_samples = this._values.length;
-
- this._values[ this._current % num_samples ] = v;
- this._current += 1;
- if(this._current > num_samples)
- this._current = 0;
-
- var avr = 0;
- for(var i = 0; i < num_samples; ++i)
- avr += this._values[i];
-
- this.setOutputData( 0, avr / num_samples );
-}
-
-MathAverageFilter.prototype.onPropertyChanged = function( name, value )
-{
- if(value < 1)
- value = 1;
- this.properties.samples = Math.round(value);
- var old = this._values;
-
- this._values = new Float32Array( this.properties.samples );
- if(old.length <= this._values.length )
- this._values.set(old);
- else
- this._values.set( old.subarray( 0, this._values.length ) );
-}
-
-LiteGraph.registerNodeType("math/average", MathAverageFilter );
-
-
-//Math
-function MathTendTo()
-{
- this.addInput("in","number");
- this.addOutput("out","number");
- this.addProperty( "factor", 0.1 );
- this.size = [60,20];
- this._value = null;
-}
-
-MathTendTo.title = "TendTo";
-MathTendTo.desc = "moves the output value always closer to the input";
-
-MathTendTo.prototype.onExecute = function()
-{
- var v = this.getInputData(0);
- if(v == null)
- v = 0;
- var f = this.properties.factor;
- if(this._value == null)
- this._value = v;
- else
- this._value = this._value * (1 - f) + v * f;
- this.setOutputData( 0, this._value );
-}
-
-LiteGraph.registerNodeType("math/tendTo", MathTendTo );
-
-
-//Math operation
-function MathOperation()
-{
- this.addInput("A","number");
- this.addInput("B","number");
- this.addOutput("=","number");
- this.addProperty( "A", 1 );
- this.addProperty( "B", 1 );
- this.addProperty( "OP", "+", "enum", { values: MathOperation.values } );
-}
-
-MathOperation.values = ["+","-","*","/","%","^"];
-
-MathOperation.title = "Operation";
-MathOperation.desc = "Easy math operators";
-MathOperation["@OP"] = { type:"enum", title: "operation", values: MathOperation.values };
-
-MathOperation.prototype.getTitle = function()
-{
- return "A " + this.properties.OP + " B";
-}
-
-MathOperation.prototype.setValue = function(v)
-{
- if( typeof(v) == "string") v = parseFloat(v);
- this.properties["value"] = v;
-}
-
-MathOperation.prototype.onExecute = function()
-{
- var A = this.getInputData(0);
- var B = this.getInputData(1);
- if(A!=null)
- this.properties["A"] = A;
- else
- A = this.properties["A"];
-
- if(B!=null)
- this.properties["B"] = B;
- else
- B = this.properties["B"];
-
- var result = 0;
- switch(this.properties.OP)
- {
- case '+': result = A+B; break;
- case '-': result = A-B; break;
- case 'x':
- case 'X':
- case '*': result = A*B; break;
- case '/': result = A/B; break;
- case '%': result = A%B; break;
- case '^': result = Math.pow(A,B); break;
- default:
- console.warn("Unknown operation: " + this.properties.OP);
- }
- this.setOutputData(0, result );
-}
-
-MathOperation.prototype.onDrawBackground = function(ctx)
-{
- if(this.flags.collapsed)
- return;
-
- ctx.font = "40px Arial";
- ctx.fillStyle = "#CCC";
- ctx.textAlign = "center";
- ctx.fillText(this.properties.OP, this.size[0] * 0.5, this.size[1] * 0.35 + LiteGraph.NODE_TITLE_HEIGHT );
- ctx.textAlign = "left";
-}
-
-LiteGraph.registerNodeType("math/operation", MathOperation );
-
-
-//Math compare
-function MathCompare()
-{
- this.addInput( "A","number" );
- this.addInput( "B","number" );
- this.addOutput("A==B","boolean");
- this.addOutput("A!=B","boolean");
- this.addProperty( "A", 0 );
- this.addProperty( "B", 0 );
-}
-
-MathCompare.title = "Compare";
-MathCompare.desc = "compares between two values";
-
-MathCompare.prototype.onExecute = function()
-{
- var A = this.getInputData(0);
- var B = this.getInputData(1);
- if(A !== undefined)
- this.properties["A"] = A;
- else
- A = this.properties["A"];
-
- if(B !== undefined)
- this.properties["B"] = B;
- else
- B = this.properties["B"];
-
- for(var i = 0, l = this.outputs.length; i < l; ++i)
- {
- var output = this.outputs[i];
- if(!output.links || !output.links.length)
- continue;
- switch( output.name )
- {
- case "A==B": value = A==B; break;
- case "A!=B": value = A!=B; break;
- case "A>B": value = A>B; break;
- case "A=B": value = A>=B; break;
- }
- this.setOutputData(i, value );
- }
-};
-
-MathCompare.prototype.onGetOutputs = function()
-{
- return [["A==B","boolean"],["A!=B","boolean"],["A>B","boolean"],["A=B","boolean"],["A<=B","boolean"]];
-}
-
-LiteGraph.registerNodeType("math/compare",MathCompare);
-
-function MathCondition()
-{
- this.addInput("A","number");
- this.addInput("B","number");
- this.addOutput("out","boolean");
- this.addProperty( "A", 1 );
- this.addProperty( "B", 1 );
- this.addProperty( "OP", ">", "string", { values: MathCondition.values } );
-
- this.size = [60,40];
-}
-
-MathCondition.values = [">","<","==","!=","<=",">="];
-MathCondition["@OP"] = { type:"enum", title: "operation", values: MathCondition.values };
-
-MathCondition.title = "Condition";
-MathCondition.desc = "evaluates condition between A and B";
-
-MathCondition.prototype.onExecute = function()
-{
- var A = this.getInputData(0);
- if(A === undefined)
- A = this.properties.A;
- else
- this.properties.A = A;
-
- var B = this.getInputData(1);
- if(B === undefined)
- B = this.properties.B;
- else
- this.properties.B = B;
-
- var result = true;
- switch(this.properties.OP)
- {
- case ">": result = A>B; break;
- case "<": result = A=": result = A>=B; break;
- }
-
- this.setOutputData(0, result );
-}
-
-LiteGraph.registerNodeType("math/condition", MathCondition);
-
-
-function MathAccumulate()
-{
- this.addInput("inc","number");
- this.addOutput("total","number");
- this.addProperty( "increment", 1 );
- this.addProperty( "value", 0 );
-}
-
-MathAccumulate.title = "Accumulate";
-MathAccumulate.desc = "Increments a value every time";
-
-MathAccumulate.prototype.onExecute = function()
-{
- if(this.properties.value === null)
- this.properties.value = 0;
-
- var inc = this.getInputData(0);
- if(inc !== null)
- this.properties.value += inc;
- else
- this.properties.value += this.properties.increment;
- this.setOutputData(0, this.properties.value );
-}
-
-LiteGraph.registerNodeType("math/accumulate", MathAccumulate);
-
-//Math Trigonometry
-function MathTrigonometry()
-{
- this.addInput("v","number");
- this.addOutput("sin","number");
-
- this.addProperty( "amplitude", 1 );
- this.addProperty( "offset", 0 );
- this.bgImageUrl = "nodes/imgs/icon-sin.png";
-}
-
-MathTrigonometry.title = "Trigonometry";
-MathTrigonometry.desc = "Sin Cos Tan";
-MathTrigonometry.filter = "shader";
-
-MathTrigonometry.prototype.onExecute = function()
-{
- var v = this.getInputData(0);
- if(v == null)
- v = 0;
- var amplitude = this.properties["amplitude"];
- var slot = this.findInputSlot("amplitude");
- if(slot != -1)
- amplitude = this.getInputData(slot);
- var offset = this.properties["offset"];
- slot = this.findInputSlot("offset");
- if(slot != -1)
- offset = this.getInputData(slot);
-
- for(var i = 0, l = this.outputs.length; i < l; ++i)
- {
- var output = this.outputs[i];
- switch( output.name )
- {
- case "sin": value = Math.sin(v); break;
- case "cos": value = Math.cos(v); break;
- case "tan": value = Math.tan(v); break;
- case "asin": value = Math.asin(v); break;
- case "acos": value = Math.acos(v); break;
- case "atan": value = Math.atan(v); break;
- }
- this.setOutputData(i, amplitude * value + offset);
- }
-}
-
-MathTrigonometry.prototype.onGetInputs = function()
-{
- return [["v","number"],["amplitude","number"],["offset","number"]];
-}
-
-
-MathTrigonometry.prototype.onGetOutputs = function()
-{
- return [["sin","number"],["cos","number"],["tan","number"],["asin","number"],["acos","number"],["atan","number"]];
-}
-
-
-LiteGraph.registerNodeType("math/trigonometry", MathTrigonometry );
-
-
-
-//math library for safe math operations without eval
-if(typeof(math) != undefined)
-{
- function MathFormula()
- {
- this.addInputs("x","number");
- this.addInputs("y","number");
- this.addOutputs("","number");
- this.properties = {x:1.0, y:1.0, formula:"x+y"};
- }
-
- MathFormula.title = "Formula";
- MathFormula.desc = "Compute safe formula";
-
- MathFormula.prototype.onExecute = function()
- {
- var x = this.getInputData(0);
- var y = this.getInputData(1);
- if(x != null)
- this.properties["x"] = x;
- else
- x = this.properties["x"];
-
- if(y!=null)
- this.properties["y"] = y;
- else
- y = this.properties["y"];
-
- var f = this.properties["formula"];
- var value = math.eval(f,{x:x,y:y,T: this.graph.globaltime });
- this.setOutputData(0, value );
- }
-
- MathFormula.prototype.onDrawBackground = function()
- {
- var f = this.properties["formula"];
- this.outputs[0].label = f;
- }
-
- MathFormula.prototype.onGetOutputs = function()
- {
- return [["A-B","number"],["A*B","number"],["A/B","number"]];
- }
-
- LiteGraph.registerNodeType("math/formula", MathFormula );
-}
-
-
-function Math3DVec2ToXYZ()
-{
- this.addInput("vec2","vec2");
- this.addOutput("x","number");
- this.addOutput("y","number");
-}
-
-Math3DVec2ToXYZ.title = "Vec2->XY";
-Math3DVec2ToXYZ.desc = "vector 2 to components";
-
-Math3DVec2ToXYZ.prototype.onExecute = function()
-{
- var v = this.getInputData(0);
- if(v == null) return;
-
- this.setOutputData( 0, v[0] );
- this.setOutputData( 1, v[1] );
-}
-
-LiteGraph.registerNodeType("math3d/vec2-to-xyz", Math3DVec2ToXYZ );
-
-
-function Math3DXYToVec2()
-{
- this.addInputs([["x","number"],["y","number"]]);
- this.addOutput("vec2","vec2");
- this.properties = {x:0, y:0};
- this._data = new Float32Array(2);
-}
-
-Math3DXYToVec2.title = "XY->Vec2";
-Math3DXYToVec2.desc = "components to vector2";
-
-Math3DXYToVec2.prototype.onExecute = function()
-{
- var x = this.getInputData(0);
- if(x == null) x = this.properties.x;
- var y = this.getInputData(1);
- if(y == null) y = this.properties.y;
-
- var data = this._data;
- data[0] = x;
- data[1] = y;
-
- this.setOutputData( 0, data );
-}
-
-LiteGraph.registerNodeType("math3d/xy-to-vec2", Math3DXYToVec2 );
-
-
-
-
-function Math3DVec3ToXYZ()
-{
- this.addInput("vec3","vec3");
- this.addOutput("x","number");
- this.addOutput("y","number");
- this.addOutput("z","number");
-}
-
-Math3DVec3ToXYZ.title = "Vec3->XYZ";
-Math3DVec3ToXYZ.desc = "vector 3 to components";
-
-Math3DVec3ToXYZ.prototype.onExecute = function()
-{
- var v = this.getInputData(0);
- if(v == null) return;
-
- this.setOutputData( 0, v[0] );
- this.setOutputData( 1, v[1] );
- this.setOutputData( 2, v[2] );
-}
-
-LiteGraph.registerNodeType("math3d/vec3-to-xyz", Math3DVec3ToXYZ );
-
-
-function Math3DXYZToVec3()
-{
- this.addInputs([["x","number"],["y","number"],["z","number"]]);
- this.addOutput("vec3","vec3");
- this.properties = {x:0, y:0, z:0};
- this._data = new Float32Array(3);
-}
-
-Math3DXYZToVec3.title = "XYZ->Vec3";
-Math3DXYZToVec3.desc = "components to vector3";
-
-Math3DXYZToVec3.prototype.onExecute = function()
-{
- var x = this.getInputData(0);
- if(x == null) x = this.properties.x;
- var y = this.getInputData(1);
- if(y == null) y = this.properties.y;
- var z = this.getInputData(2);
- if(z == null) z = this.properties.z;
-
- var data = this._data;
- data[0] = x;
- data[1] = y;
- data[2] = z;
-
- this.setOutputData( 0, data );
-}
-
-LiteGraph.registerNodeType("math3d/xyz-to-vec3", Math3DXYZToVec3 );
-
-
-
-function Math3DVec4ToXYZW()
-{
- this.addInput("vec4","vec4");
- this.addOutput("x","number");
- this.addOutput("y","number");
- this.addOutput("z","number");
- this.addOutput("w","number");
-}
-
-Math3DVec4ToXYZW.title = "Vec4->XYZW";
-Math3DVec4ToXYZW.desc = "vector 4 to components";
-
-Math3DVec4ToXYZW.prototype.onExecute = function()
-{
- var v = this.getInputData(0);
- if(v == null) return;
-
- this.setOutputData( 0, v[0] );
- this.setOutputData( 1, v[1] );
- this.setOutputData( 2, v[2] );
- this.setOutputData( 3, v[3] );
-}
-
-LiteGraph.registerNodeType("math3d/vec4-to-xyzw", Math3DVec4ToXYZW );
-
-
-function Math3DXYZWToVec4()
-{
- this.addInputs([["x","number"],["y","number"],["z","number"],["w","number"]]);
- this.addOutput("vec4","vec4");
- this.properties = {x:0, y:0, z:0, w:0};
- this._data = new Float32Array(4);
-}
-
-Math3DXYZWToVec4.title = "XYZW->Vec4";
-Math3DXYZWToVec4.desc = "components to vector4";
-
-Math3DXYZWToVec4.prototype.onExecute = function()
-{
- var x = this.getInputData(0);
- if(x == null) x = this.properties.x;
- var y = this.getInputData(1);
- if(y == null) y = this.properties.y;
- var z = this.getInputData(2);
- if(z == null) z = this.properties.z;
- var w = this.getInputData(3);
- if(w == null) w = this.properties.w;
-
- var data = this._data;
- data[0] = x;
- data[1] = y;
- data[2] = z;
- data[3] = w;
-
- this.setOutputData( 0, data );
-}
-
-LiteGraph.registerNodeType("math3d/xyzw-to-vec4", Math3DXYZWToVec4 );
-
-
-
-
-//if glMatrix is installed...
-if(global.glMatrix)
-{
-
- function Math3DQuaternion()
- {
- this.addOutput("quat","quat");
- this.properties = { x:0, y:0, z:0, w: 1 };
- this._value = quat.create();
- }
-
- Math3DQuaternion.title = "Quaternion";
- Math3DQuaternion.desc = "quaternion";
-
- Math3DQuaternion.prototype.onExecute = function()
- {
- this._value[0] = this.properties.x;
- this._value[1] = this.properties.y;
- this._value[2] = this.properties.z;
- this._value[3] = this.properties.w;
- this.setOutputData( 0, this._value );
- }
-
- LiteGraph.registerNodeType("math3d/quaternion", Math3DQuaternion );
-
-
- function Math3DRotation()
- {
- this.addInputs([["degrees","number"],["axis","vec3"]]);
- this.addOutput("quat","quat");
- this.properties = { angle:90.0, axis: vec3.fromValues(0,1,0) };
-
- this._value = quat.create();
- }
-
- Math3DRotation.title = "Rotation";
- Math3DRotation.desc = "quaternion rotation";
-
- Math3DRotation.prototype.onExecute = function()
- {
- var angle = this.getInputData(0);
- if(angle == null) angle = this.properties.angle;
- var axis = this.getInputData(1);
- if(axis == null) axis = this.properties.axis;
-
- var R = quat.setAxisAngle( this._value, axis, angle * 0.0174532925 );
- this.setOutputData( 0, R );
- }
-
-
- LiteGraph.registerNodeType("math3d/rotation", Math3DRotation );
-
-
- //Math3D rotate vec3
- function Math3DRotateVec3()
- {
- this.addInputs([["vec3","vec3"],["quat","quat"]]);
- this.addOutput("result","vec3");
- this.properties = { vec: [0,0,1] };
- }
-
- Math3DRotateVec3.title = "Rot. Vec3";
- Math3DRotateVec3.desc = "rotate a point";
-
- Math3DRotateVec3.prototype.onExecute = function()
- {
- var vec = this.getInputData(0);
- if(vec == null) vec = this.properties.vec;
- var quat = this.getInputData(1);
- if(quat == null)
- this.setOutputData(vec);
- else
- this.setOutputData( 0, vec3.transformQuat( vec3.create(), vec, quat ) );
- }
-
- LiteGraph.registerNodeType("math3d/rotate_vec3", Math3DRotateVec3);
-
-
-
- function Math3DMultQuat()
- {
- this.addInputs( [["A","quat"],["B","quat"]] );
- this.addOutput( "A*B","quat" );
-
- this._value = quat.create();
- }
-
- Math3DMultQuat.title = "Mult. Quat";
- Math3DMultQuat.desc = "rotate quaternion";
-
- Math3DMultQuat.prototype.onExecute = function()
- {
- var A = this.getInputData(0);
- if(A == null) return;
- var B = this.getInputData(1);
- if(B == null) return;
-
- var R = quat.multiply( this._value, A, B );
- this.setOutputData( 0, R );
- }
-
- LiteGraph.registerNodeType("math3d/mult-quat", Math3DMultQuat );
-
-
- function Math3DQuatSlerp()
- {
- this.addInputs( [["A","quat"],["B","quat"],["factor","number"]] );
- this.addOutput( "slerp","quat" );
- this.addProperty( "factor", 0.5 );
-
- this._value = quat.create();
- }
-
- Math3DQuatSlerp.title = "Quat Slerp";
- Math3DQuatSlerp.desc = "quaternion spherical interpolation";
-
- Math3DQuatSlerp.prototype.onExecute = function()
- {
- var A = this.getInputData(0);
- if(A == null)
- return;
- var B = this.getInputData(1);
- if(B == null)
- return;
- var factor = this.properties.factor;
- if( this.getInputData(2) != null )
- factor = this.getInputData(2);
-
- var R = quat.slerp( this._value, A, B, factor );
- this.setOutputData( 0, R );
- }
-
- LiteGraph.registerNodeType("math3d/quat-slerp", Math3DQuatSlerp );
-
-} //glMatrix
-
+(function(global){
+var LiteGraph = global.LiteGraph;
+
+//Converter
+function Converter()
+{
+ this.addInput("in","*");
+ this.size = [60,20];
+}
+
+Converter.title = "Converter";
+Converter.desc = "type A to type B";
+
+Converter.prototype.onExecute = function()
+{
+ var v = this.getInputData(0);
+ if(v == null)
+ return;
+
+ if(this.outputs)
+ for(var i = 0; i < this.outputs.length; i++)
+ {
+ var output = this.outputs[i];
+ if(!output.links || !output.links.length)
+ continue;
+
+ var result = null;
+ switch( output.name )
+ {
+ case "number": result = v.length ? v[0] : parseFloat(v); break;
+ case "vec2":
+ case "vec3":
+ case "vec4":
+ var result = null;
+ var count = 1;
+ switch(output.name)
+ {
+ case "vec2": count = 2; break;
+ case "vec3": count = 3; break;
+ case "vec4": count = 4; break;
+ }
+
+ var result = new Float32Array( count );
+ if( v.length )
+ {
+ for(var j = 0; j < v.length && j < result.length; j++)
+ result[j] = v[j];
+ }
+ else
+ result[0] = parseFloat(v);
+ break;
+ }
+ this.setOutputData(i, result);
+ }
+}
+
+Converter.prototype.onGetOutputs = function() {
+ return [["number","number"],["vec2","vec2"],["vec3","vec3"],["vec4","vec4"]];
+}
+
+LiteGraph.registerNodeType("math/converter", Converter );
+
+
+//Bypass
+function Bypass()
+{
+ this.addInput("in");
+ this.addOutput("out");
+ this.size = [60,20];
+}
+
+Bypass.title = "Bypass";
+Bypass.desc = "removes the type";
+
+Bypass.prototype.onExecute = function()
+{
+ var v = this.getInputData(0);
+ this.setOutputData(0, v);
+}
+
+LiteGraph.registerNodeType("math/bypass", Bypass );
+
+
+
+function MathRange()
+{
+ this.addInput("in","number",{locked:true});
+ this.addOutput("out","number",{locked:true});
+
+ this.addProperty( "in", 0 );
+ this.addProperty( "in_min", 0 );
+ this.addProperty( "in_max", 1 );
+ this.addProperty( "out_min", 0 );
+ this.addProperty( "out_max", 1 );
+
+ this.size = [80,20];
+}
+
+MathRange.title = "Range";
+MathRange.desc = "Convert a number from one range to another";
+
+MathRange.prototype.getTitle = function()
+{
+ if(this.flags.collapsed)
+ return (this._last_v || 0).toFixed(2);
+ return this.title;
+}
+
+MathRange.prototype.onExecute = function()
+{
+ if(this.inputs)
+ for(var i = 0; i < this.inputs.length; i++)
+ {
+ var input = this.inputs[i];
+ var v = this.getInputData(i);
+ if(v === undefined)
+ continue;
+ this.properties[ input.name ] = v;
+ }
+
+ var v = this.properties["in"];
+ if(v === undefined || v === null || v.constructor !== Number)
+ v = 0;
+
+ var in_min = this.properties.in_min;
+ var in_max = this.properties.in_max;
+ var out_min = this.properties.out_min;
+ var out_max = this.properties.out_max;
+
+ this._last_v = ((v - in_min) / (in_max - in_min)) * (out_max - out_min) + out_min;
+ this.setOutputData(0, this._last_v );
+}
+
+MathRange.prototype.onDrawBackground = function(ctx)
+{
+ //show the current value
+ if(this._last_v)
+ this.outputs[0].label = this._last_v.toFixed(3);
+ else
+ this.outputs[0].label = "?";
+}
+
+MathRange.prototype.onGetInputs = function() {
+ return [["in_min","number"],["in_max","number"],["out_min","number"],["out_max","number"]];
+}
+
+LiteGraph.registerNodeType("math/range", MathRange);
+
+
+
+function MathRand()
+{
+ this.addOutput("value","number");
+ this.addProperty( "min", 0 );
+ this.addProperty( "max", 1 );
+ this.size = [60,20];
+}
+
+MathRand.title = "Rand";
+MathRand.desc = "Random number";
+
+MathRand.prototype.onExecute = function()
+{
+ if(this.inputs)
+ for(var i = 0; i < this.inputs.length; i++)
+ {
+ var input = this.inputs[i];
+ var v = this.getInputData(i);
+ if(v === undefined)
+ continue;
+ this.properties[input.name] = v;
+ }
+
+ var min = this.properties.min;
+ var max = this.properties.max;
+ this._last_v = Math.random() * (max-min) + min;
+ this.setOutputData(0, this._last_v );
+}
+
+MathRand.prototype.onDrawBackground = function(ctx)
+{
+ //show the current value
+ if(this._last_v)
+ this.outputs[0].label = this._last_v.toFixed(3);
+ else
+ this.outputs[0].label = "?";
+}
+
+MathRand.prototype.onGetInputs = function() {
+ return [["min","number"],["max","number"]];
+}
+
+LiteGraph.registerNodeType("math/rand", MathRand);
+
+//Math clamp
+function MathClamp()
+{
+ this.addInput("in","number");
+ this.addOutput("out","number");
+ this.size = [60,20];
+ this.addProperty( "min", 0 );
+ this.addProperty( "max", 1 );
+}
+
+MathClamp.title = "Clamp";
+MathClamp.desc = "Clamp number between min and max";
+MathClamp.filter = "shader";
+
+MathClamp.prototype.onExecute = function()
+{
+ var v = this.getInputData(0);
+ if(v == null) return;
+ v = Math.max(this.properties.min,v);
+ v = Math.min(this.properties.max,v);
+ this.setOutputData(0, v );
+}
+
+MathClamp.prototype.getCode = function(lang)
+{
+ var code = "";
+ if(this.isInputConnected(0))
+ code += "clamp({{0}}," + this.properties.min + "," + this.properties.max + ")";
+ return code;
+}
+
+LiteGraph.registerNodeType("math/clamp", MathClamp );
+
+
+
+//Math ABS
+function MathLerp()
+{
+ this.properties = { f: 0.5 };
+ this.addInput("A","number");
+ this.addInput("B","number");
+
+ this.addOutput("out","number");
+}
+
+MathLerp.title = "Lerp";
+MathLerp.desc = "Linear Interpolation";
+
+MathLerp.prototype.onExecute = function()
+{
+ var v1 = this.getInputData(0);
+ if(v1 == null)
+ v1 = 0;
+ var v2 = this.getInputData(1);
+ if(v2 == null)
+ v2 = 0;
+
+ var f = this.properties.f;
+
+ var _f = this.getInputData(2);
+ if(_f !== undefined)
+ f = _f;
+
+ this.setOutputData(0, v1 * (1-f) + v2 * f );
+}
+
+MathLerp.prototype.onGetInputs = function()
+{
+ return [["f","number"]];
+}
+
+LiteGraph.registerNodeType("math/lerp", MathLerp);
+
+
+
+//Math ABS
+function MathAbs()
+{
+ this.addInput("in","number");
+ this.addOutput("out","number");
+ this.size = [60,20];
+}
+
+MathAbs.title = "Abs";
+MathAbs.desc = "Absolute";
+
+MathAbs.prototype.onExecute = function()
+{
+ var v = this.getInputData(0);
+ if(v == null) return;
+ this.setOutputData(0, Math.abs(v) );
+}
+
+LiteGraph.registerNodeType("math/abs", MathAbs);
+
+
+//Math Floor
+function MathFloor()
+{
+ this.addInput("in","number");
+ this.addOutput("out","number");
+ this.size = [60,20];
+}
+
+MathFloor.title = "Floor";
+MathFloor.desc = "Floor number to remove fractional part";
+
+MathFloor.prototype.onExecute = function()
+{
+ var v = this.getInputData(0);
+ if(v == null) return;
+ this.setOutputData(0, Math.floor(v) );
+}
+
+LiteGraph.registerNodeType("math/floor", MathFloor );
+
+
+//Math frac
+function MathFrac()
+{
+ this.addInput("in","number");
+ this.addOutput("out","number");
+ this.size = [60,20];
+}
+
+MathFrac.title = "Frac";
+MathFrac.desc = "Returns fractional part";
+
+MathFrac.prototype.onExecute = function()
+{
+ var v = this.getInputData(0);
+ if(v == null)
+ return;
+ this.setOutputData(0, v%1 );
+}
+
+LiteGraph.registerNodeType("math/frac",MathFrac);
+
+
+//Math Floor
+function MathSmoothStep()
+{
+ this.addInput("in","number");
+ this.addOutput("out","number");
+ this.size = [60,20];
+ this.properties = { A: 0, B: 1 };
+}
+
+MathSmoothStep.title = "Smoothstep";
+MathSmoothStep.desc = "Smoothstep";
+
+MathSmoothStep.prototype.onExecute = function()
+{
+ var v = this.getInputData(0);
+ if(v === undefined)
+ return;
+
+ var edge0 = this.properties.A;
+ var edge1 = this.properties.B;
+
+ // Scale, bias and saturate x to 0..1 range
+ v = Math.clamp((v - edge0)/(edge1 - edge0), 0.0, 1.0);
+ // Evaluate polynomial
+ v = v*v*(3 - 2*v);
+
+ this.setOutputData(0, v );
+}
+
+LiteGraph.registerNodeType("math/smoothstep", MathSmoothStep );
+
+//Math scale
+function MathScale()
+{
+ this.addInput("in","number",{label:""});
+ this.addOutput("out","number",{label:""});
+ this.size = [60,20];
+ this.addProperty( "factor", 1 );
+}
+
+MathScale.title = "Scale";
+MathScale.desc = "v * factor";
+
+MathScale.prototype.onExecute = function()
+{
+ var value = this.getInputData(0);
+ if(value != null)
+ this.setOutputData(0, value * this.properties.factor );
+}
+
+LiteGraph.registerNodeType("math/scale", MathScale );
+
+
+//Math Average
+function MathAverageFilter()
+{
+ this.addInput("in","number");
+ this.addOutput("out","number");
+ this.size = [60,20];
+ this.addProperty( "samples", 10 );
+ this._values = new Float32Array(10);
+ this._current = 0;
+}
+
+MathAverageFilter.title = "Average";
+MathAverageFilter.desc = "Average Filter";
+
+MathAverageFilter.prototype.onExecute = function()
+{
+ var v = this.getInputData(0);
+ if(v == null)
+ v = 0;
+
+ var num_samples = this._values.length;
+
+ this._values[ this._current % num_samples ] = v;
+ this._current += 1;
+ if(this._current > num_samples)
+ this._current = 0;
+
+ var avr = 0;
+ for(var i = 0; i < num_samples; ++i)
+ avr += this._values[i];
+
+ this.setOutputData( 0, avr / num_samples );
+}
+
+MathAverageFilter.prototype.onPropertyChanged = function( name, value )
+{
+ if(value < 1)
+ value = 1;
+ this.properties.samples = Math.round(value);
+ var old = this._values;
+
+ this._values = new Float32Array( this.properties.samples );
+ if(old.length <= this._values.length )
+ this._values.set(old);
+ else
+ this._values.set( old.subarray( 0, this._values.length ) );
+}
+
+LiteGraph.registerNodeType("math/average", MathAverageFilter );
+
+
+//Math
+function MathTendTo()
+{
+ this.addInput("in","number");
+ this.addOutput("out","number");
+ this.addProperty( "factor", 0.1 );
+ this.size = [60,20];
+ this._value = null;
+}
+
+MathTendTo.title = "TendTo";
+MathTendTo.desc = "moves the output value always closer to the input";
+
+MathTendTo.prototype.onExecute = function()
+{
+ var v = this.getInputData(0);
+ if(v == null)
+ v = 0;
+ var f = this.properties.factor;
+ if(this._value == null)
+ this._value = v;
+ else
+ this._value = this._value * (1 - f) + v * f;
+ this.setOutputData( 0, this._value );
+}
+
+LiteGraph.registerNodeType("math/tendTo", MathTendTo );
+
+
+//Math operation
+function MathOperation()
+{
+ this.addInput("A","number");
+ this.addInput("B","number");
+ this.addOutput("=","number");
+ this.addProperty( "A", 1 );
+ this.addProperty( "B", 1 );
+ this.addProperty( "OP", "+", "enum", { values: MathOperation.values } );
+}
+
+MathOperation.values = ["+","-","*","/","%","^"];
+
+MathOperation.title = "Operation";
+MathOperation.desc = "Easy math operators";
+MathOperation["@OP"] = { type:"enum", title: "operation", values: MathOperation.values };
+
+MathOperation.prototype.getTitle = function()
+{
+ return "A " + this.properties.OP + " B";
+}
+
+MathOperation.prototype.setValue = function(v)
+{
+ if( typeof(v) == "string") v = parseFloat(v);
+ this.properties["value"] = v;
+}
+
+MathOperation.prototype.onExecute = function()
+{
+ var A = this.getInputData(0);
+ var B = this.getInputData(1);
+ if(A!=null)
+ this.properties["A"] = A;
+ else
+ A = this.properties["A"];
+
+ if(B!=null)
+ this.properties["B"] = B;
+ else
+ B = this.properties["B"];
+
+ var result = 0;
+ switch(this.properties.OP)
+ {
+ case '+': result = A+B; break;
+ case '-': result = A-B; break;
+ case 'x':
+ case 'X':
+ case '*': result = A*B; break;
+ case '/': result = A/B; break;
+ case '%': result = A%B; break;
+ case '^': result = Math.pow(A,B); break;
+ default:
+ console.warn("Unknown operation: " + this.properties.OP);
+ }
+ this.setOutputData(0, result );
+}
+
+MathOperation.prototype.onDrawBackground = function(ctx)
+{
+ if(this.flags.collapsed)
+ return;
+
+ ctx.font = "40px Arial";
+ ctx.fillStyle = "#CCC";
+ ctx.textAlign = "center";
+ ctx.fillText(this.properties.OP, this.size[0] * 0.5, this.size[1] * 0.35 + LiteGraph.NODE_TITLE_HEIGHT );
+ ctx.textAlign = "left";
+}
+
+LiteGraph.registerNodeType("math/operation", MathOperation );
+
+
+//Math compare
+function MathCompare()
+{
+ this.addInput( "A","number" );
+ this.addInput( "B","number" );
+ this.addOutput("A==B","boolean");
+ this.addOutput("A!=B","boolean");
+ this.addProperty( "A", 0 );
+ this.addProperty( "B", 0 );
+}
+
+MathCompare.title = "Compare";
+MathCompare.desc = "compares between two values";
+
+MathCompare.prototype.onExecute = function()
+{
+ var A = this.getInputData(0);
+ var B = this.getInputData(1);
+ if(A !== undefined)
+ this.properties["A"] = A;
+ else
+ A = this.properties["A"];
+
+ if(B !== undefined)
+ this.properties["B"] = B;
+ else
+ B = this.properties["B"];
+
+ for(var i = 0, l = this.outputs.length; i < l; ++i)
+ {
+ var output = this.outputs[i];
+ if(!output.links || !output.links.length)
+ continue;
+ switch( output.name )
+ {
+ case "A==B": value = A==B; break;
+ case "A!=B": value = A!=B; break;
+ case "A>B": value = A>B; break;
+ case "A=B": value = A>=B; break;
+ }
+ this.setOutputData(i, value );
+ }
+};
+
+MathCompare.prototype.onGetOutputs = function()
+{
+ return [["A==B","boolean"],["A!=B","boolean"],["A>B","boolean"],["A=B","boolean"],["A<=B","boolean"]];
+}
+
+LiteGraph.registerNodeType("math/compare",MathCompare);
+
+function MathCondition()
+{
+ this.addInput("A","number");
+ this.addInput("B","number");
+ this.addOutput("out","boolean");
+ this.addProperty( "A", 1 );
+ this.addProperty( "B", 1 );
+ this.addProperty( "OP", ">", "string", { values: MathCondition.values } );
+
+ this.size = [60,40];
+}
+
+MathCondition.values = [">","<","==","!=","<=",">="];
+MathCondition["@OP"] = { type:"enum", title: "operation", values: MathCondition.values };
+
+MathCondition.title = "Condition";
+MathCondition.desc = "evaluates condition between A and B";
+
+MathCondition.prototype.onExecute = function()
+{
+ var A = this.getInputData(0);
+ if(A === undefined)
+ A = this.properties.A;
+ else
+ this.properties.A = A;
+
+ var B = this.getInputData(1);
+ if(B === undefined)
+ B = this.properties.B;
+ else
+ this.properties.B = B;
+
+ var result = true;
+ switch(this.properties.OP)
+ {
+ case ">": result = A>B; break;
+ case "<": result = A=": result = A>=B; break;
+ }
+
+ this.setOutputData(0, result );
+}
+
+LiteGraph.registerNodeType("math/condition", MathCondition);
+
+
+function MathAccumulate()
+{
+ this.addInput("inc","number");
+ this.addOutput("total","number");
+ this.addProperty( "increment", 1 );
+ this.addProperty( "value", 0 );
+}
+
+MathAccumulate.title = "Accumulate";
+MathAccumulate.desc = "Increments a value every time";
+
+MathAccumulate.prototype.onExecute = function()
+{
+ if(this.properties.value === null)
+ this.properties.value = 0;
+
+ var inc = this.getInputData(0);
+ if(inc !== null)
+ this.properties.value += inc;
+ else
+ this.properties.value += this.properties.increment;
+ this.setOutputData(0, this.properties.value );
+}
+
+LiteGraph.registerNodeType("math/accumulate", MathAccumulate);
+
+//Math Trigonometry
+function MathTrigonometry()
+{
+ this.addInput("v","number");
+ this.addOutput("sin","number");
+
+ this.addProperty( "amplitude", 1 );
+ this.addProperty( "offset", 0 );
+ this.bgImageUrl = "nodes/imgs/icon-sin.png";
+}
+
+MathTrigonometry.title = "Trigonometry";
+MathTrigonometry.desc = "Sin Cos Tan";
+MathTrigonometry.filter = "shader";
+
+MathTrigonometry.prototype.onExecute = function()
+{
+ var v = this.getInputData(0);
+ if(v == null)
+ v = 0;
+ var amplitude = this.properties["amplitude"];
+ var slot = this.findInputSlot("amplitude");
+ if(slot != -1)
+ amplitude = this.getInputData(slot);
+ var offset = this.properties["offset"];
+ slot = this.findInputSlot("offset");
+ if(slot != -1)
+ offset = this.getInputData(slot);
+
+ for(var i = 0, l = this.outputs.length; i < l; ++i)
+ {
+ var output = this.outputs[i];
+ switch( output.name )
+ {
+ case "sin": value = Math.sin(v); break;
+ case "cos": value = Math.cos(v); break;
+ case "tan": value = Math.tan(v); break;
+ case "asin": value = Math.asin(v); break;
+ case "acos": value = Math.acos(v); break;
+ case "atan": value = Math.atan(v); break;
+ }
+ this.setOutputData(i, amplitude * value + offset);
+ }
+}
+
+MathTrigonometry.prototype.onGetInputs = function()
+{
+ return [["v","number"],["amplitude","number"],["offset","number"]];
+}
+
+
+MathTrigonometry.prototype.onGetOutputs = function()
+{
+ return [["sin","number"],["cos","number"],["tan","number"],["asin","number"],["acos","number"],["atan","number"]];
+}
+
+
+LiteGraph.registerNodeType("math/trigonometry", MathTrigonometry );
+
+
+
+//math library for safe math operations without eval
+if(typeof(math) != undefined)
+{
+ function MathFormula()
+ {
+ this.addInputs("x","number");
+ this.addInputs("y","number");
+ this.addOutputs("","number");
+ this.properties = {x:1.0, y:1.0, formula:"x+y"};
+ }
+
+ MathFormula.title = "Formula";
+ MathFormula.desc = "Compute safe formula";
+
+ MathFormula.prototype.onExecute = function()
+ {
+ var x = this.getInputData(0);
+ var y = this.getInputData(1);
+ if(x != null)
+ this.properties["x"] = x;
+ else
+ x = this.properties["x"];
+
+ if(y!=null)
+ this.properties["y"] = y;
+ else
+ y = this.properties["y"];
+
+ var f = this.properties["formula"];
+ var value = math.eval(f,{x:x,y:y,T: this.graph.globaltime });
+ this.setOutputData(0, value );
+ }
+
+ MathFormula.prototype.onDrawBackground = function()
+ {
+ var f = this.properties["formula"];
+ this.outputs[0].label = f;
+ }
+
+ MathFormula.prototype.onGetOutputs = function()
+ {
+ return [["A-B","number"],["A*B","number"],["A/B","number"]];
+ }
+
+ LiteGraph.registerNodeType("math/formula", MathFormula );
+}
+
+
+function Math3DVec2ToXYZ()
+{
+ this.addInput("vec2","vec2");
+ this.addOutput("x","number");
+ this.addOutput("y","number");
+}
+
+Math3DVec2ToXYZ.title = "Vec2->XY";
+Math3DVec2ToXYZ.desc = "vector 2 to components";
+
+Math3DVec2ToXYZ.prototype.onExecute = function()
+{
+ var v = this.getInputData(0);
+ if(v == null) return;
+
+ this.setOutputData( 0, v[0] );
+ this.setOutputData( 1, v[1] );
+}
+
+LiteGraph.registerNodeType("math3d/vec2-to-xyz", Math3DVec2ToXYZ );
+
+
+function Math3DXYToVec2()
+{
+ this.addInputs([["x","number"],["y","number"]]);
+ this.addOutput("vec2","vec2");
+ this.properties = {x:0, y:0};
+ this._data = new Float32Array(2);
+}
+
+MathRand.prototype.onDrawBackground = function(ctx)
+{
+ //show the current value
+ if(this._last_v)
+ this.outputs[0].label = this._last_v.toFixed(3);
+ else
+ this.outputs[0].label = "?";
+}
+
+MathRand.prototype.onGetInputs = function() {
+ return [["min","number"],["max","number"]];
+}
+
+LiteGraph.registerNodeType("math/rand", MathRand);
+
+//Math clamp
+function MathClamp()
+{
+ this.addInput("in","number");
+ this.addOutput("out","number");
+ this.size = [60,20];
+ this.addProperty( "min", 0 );
+ this.addProperty( "max", 1 );
+}
+
+MathClamp.title = "Clamp";
+MathClamp.desc = "Clamp number between min and max";
+MathClamp.filter = "shader";
+
+MathClamp.prototype.onExecute = function()
+{
+ var v = this.getInputData(0);
+ if(v == null) return;
+ v = Math.max(this.properties.min,v);
+ v = Math.min(this.properties.max,v);
+ this.setOutputData(0, v );
+}
+
+MathClamp.prototype.getCode = function(lang)
+{
+ var code = "";
+ if(this.isInputConnected(0))
+ code += "clamp({{0}}," + this.properties.min + "," + this.properties.max + ")";
+ return code;
+}
+
+LiteGraph.registerNodeType("math/clamp", MathClamp );
+
+
+
+//Math ABS
+function MathLerp()
+{
+ this.properties = { f: 0.5 };
+ this.addInput("A","number");
+ this.addInput("B","number");
+
+ this.addOutput("out","number");
+}
+
+MathLerp.title = "Lerp";
+MathLerp.desc = "Linear Interpolation";
+
+MathLerp.prototype.onExecute = function()
+{
+ var v1 = this.getInputData(0);
+ if(v1 == null)
+ v1 = 0;
+ var v2 = this.getInputData(1);
+ if(v2 == null)
+ v2 = 0;
+
+ var f = this.properties.f;
+
+ var _f = this.getInputData(2);
+ if(_f !== undefined)
+ f = _f;
+
+ this.setOutputData(0, v1 * (1-f) + v2 * f );
+}
+
+MathLerp.prototype.onGetInputs = function()
+{
+ return [["f","number"]];
+}
+
+LiteGraph.registerNodeType("math/lerp", MathLerp);
+
+
+
+//Math ABS
+function MathAbs()
+{
+ this.addInput("in","number");
+ this.addOutput("out","number");
+ this.size = [60,20];
+}
+
+MathAbs.title = "Abs";
+MathAbs.desc = "Absolute";
+
+MathAbs.prototype.onExecute = function()
+{
+ var v = this.getInputData(0);
+ if(v == null) return;
+ this.setOutputData(0, Math.abs(v) );
+}
+
+LiteGraph.registerNodeType("math/abs", MathAbs);
+
+
+//Math Floor
+function MathFloor()
+{
+ this.addInput("in","number");
+ this.addOutput("out","number");
+ this.size = [60,20];
+}
+
+MathFloor.title = "Floor";
+MathFloor.desc = "Floor number to remove fractional part";
+
+MathFloor.prototype.onExecute = function()
+{
+ var v = this.getInputData(0);
+ if(v == null) return;
+ this.setOutputData(0, Math.floor(v) );
+}
+
+LiteGraph.registerNodeType("math/floor", MathFloor );
+
+
+//Math frac
+function MathFrac()
+{
+ this.addInput("in","number");
+ this.addOutput("out","number");
+ this.size = [60,20];
+}
+
+MathFrac.title = "Frac";
+MathFrac.desc = "Returns fractional part";
+
+MathFrac.prototype.onExecute = function()
+{
+ var v = this.getInputData(0);
+ if(v == null)
+ return;
+ this.setOutputData(0, v%1 );
+}
+
+LiteGraph.registerNodeType("math/frac",MathFrac);
+
+
+//Math Floor
+function MathSmoothStep()
+{
+ this.addInput("in","number");
+ this.addOutput("out","number");
+ this.size = [60,20];
+ this.properties = { A: 0, B: 1 };
+}
+
+MathSmoothStep.title = "Smoothstep";
+MathSmoothStep.desc = "Smoothstep";
+
+MathSmoothStep.prototype.onExecute = function()
+{
+ var v = this.getInputData(0);
+ if(v === undefined)
+ return;
+
+ var edge0 = this.properties.A;
+ var edge1 = this.properties.B;
+
+ // Scale, bias and saturate x to 0..1 range
+ v = Math.clamp((v - edge0)/(edge1 - edge0), 0.0, 1.0);
+ // Evaluate polynomial
+ v = v*v*(3 - 2*v);
+
+ this.setOutputData(0, v );
+}
+
+LiteGraph.registerNodeType("math/smoothstep", MathSmoothStep );
+
+//Math scale
+function MathScale()
+{
+ this.addInput("in","number",{label:""});
+ this.addOutput("out","number",{label:""});
+ this.size = [60,20];
+ this.addProperty( "factor", 1 );
+}
+
+MathScale.title = "Scale";
+MathScale.desc = "v * factor";
+
+MathScale.prototype.onExecute = function()
+{
+ var value = this.getInputData(0);
+ if(value != null)
+ this.setOutputData(0, value * this.properties.factor );
+}
+
+LiteGraph.registerNodeType("math/scale", MathScale );
+
+
+//Math Average
+function MathAverageFilter()
+{
+ this.addInput("in","number");
+ this.addOutput("out","number");
+ this.size = [60,20];
+ this.addProperty( "samples", 10 );
+ this._values = new Float32Array(10);
+ this._current = 0;
+}
+
+MathAverageFilter.title = "Average";
+MathAverageFilter.desc = "Average Filter";
+
+MathAverageFilter.prototype.onExecute = function()
+{
+ var v = this.getInputData(0);
+ if(v == null)
+ v = 0;
+
+ var num_samples = this._values.length;
+
+ this._values[ this._current % num_samples ] = v;
+ this._current += 1;
+ if(this._current > num_samples)
+ this._current = 0;
+
+ var avr = 0;
+ for(var i = 0; i < num_samples; ++i)
+ avr += this._values[i];
+
+ this.setOutputData( 0, avr / num_samples );
+}
+
+MathAverageFilter.prototype.onPropertyChanged = function( name, value )
+{
+ if(value < 1)
+ value = 1;
+ this.properties.samples = Math.round(value);
+ var old = this._values;
+
+ this._values = new Float32Array( this.properties.samples );
+ if(old.length <= this._values.length )
+ this._values.set(old);
+ else
+ this._values.set( old.subarray( 0, this._values.length ) );
+}
+
+LiteGraph.registerNodeType("math/average", MathAverageFilter );
+
+
+//Math
+function MathTendTo()
+{
+ this.addInput("in","number");
+ this.addOutput("out","number");
+ this.addProperty( "factor", 0.1 );
+ this.size = [60,20];
+ this._value = null;
+}
+
+MathTendTo.title = "TendTo";
+MathTendTo.desc = "moves the output value always closer to the input";
+
+MathTendTo.prototype.onExecute = function()
+{
+ var v = this.getInputData(0);
+ if(v == null)
+ v = 0;
+ var f = this.properties.factor;
+ if(this._value == null)
+ this._value = v;
+ else
+ this._value = this._value * (1 - f) + v * f;
+ this.setOutputData( 0, this._value );
+}
+
+LiteGraph.registerNodeType("math/tendTo", MathTendTo );
+
+
+//Math operation
+function MathOperation()
+{
+ this.addInput("A","number");
+ this.addInput("B","number");
+ this.addOutput("=","number");
+ this.addProperty( "A", 1 );
+ this.addProperty( "B", 1 );
+ this.addProperty( "OP", "+", "enum", { values: MathOperation.values } );
+}
+
+MathOperation.values = ["+","-","*","/","%","^"];
+
+MathOperation.title = "Operation";
+MathOperation.desc = "Easy math operators";
+MathOperation["@OP"] = { type:"enum", title: "operation", values: MathOperation.values };
+
+MathOperation.prototype.getTitle = function()
+{
+ return "A " + this.properties.OP + " B";
+}
+
+MathOperation.prototype.setValue = function(v)
+{
+ if( typeof(v) == "string") v = parseFloat(v);
+ this.properties["value"] = v;
+}
+
+MathOperation.prototype.onExecute = function()
+{
+ var A = this.getInputData(0);
+ var B = this.getInputData(1);
+ if(A!=null)
+ this.properties["A"] = A;
+ else
+ A = this.properties["A"];
+
+ if(B!=null)
+ this.properties["B"] = B;
+ else
+ B = this.properties["B"];
+
+ var result = 0;
+ switch(this.properties.OP)
+ {
+ case '+': result = A+B; break;
+ case '-': result = A-B; break;
+ case 'x':
+ case 'X':
+ case '*': result = A*B; break;
+ case '/': result = A/B; break;
+ case '%': result = A%B; break;
+ case '^': result = Math.pow(A,B); break;
+ default:
+ console.warn("Unknown operation: " + this.properties.OP);
+ }
+ this.setOutputData(0, result );
+}
+
+MathOperation.prototype.onDrawBackground = function(ctx)
+{
+ if(this.flags.collapsed)
+ return;
+
+ ctx.font = "40px Arial";
+ ctx.fillStyle = "#CCC";
+ ctx.textAlign = "center";
+ ctx.fillText(this.properties.OP, this.size[0] * 0.5, this.size[1] * 0.35 + LiteGraph.NODE_TITLE_HEIGHT );
+ ctx.textAlign = "left";
+}
+
+LiteGraph.registerNodeType("math/operation", MathOperation );
+
+
+//Math compare
+function MathCompare()
+{
+ this.addInput( "A","number" );
+ this.addInput( "B","number" );
+ this.addOutput("A==B","boolean");
+ this.addOutput("A!=B","boolean");
+ this.addProperty( "A", 0 );
+ this.addProperty( "B", 0 );
+}
+
+MathCompare.title = "Compare";
+MathCompare.desc = "compares between two values";
+
+MathCompare.prototype.onExecute = function()
+{
+ var A = this.getInputData(0);
+ var B = this.getInputData(1);
+ if(A !== undefined)
+ this.properties["A"] = A;
+ else
+ A = this.properties["A"];
+
+ if(B !== undefined)
+ this.properties["B"] = B;
+ else
+ B = this.properties["B"];
+
+ for(var i = 0, l = this.outputs.length; i < l; ++i)
+ {
+ var output = this.outputs[i];
+ if(!output.links || !output.links.length)
+ continue;
+ switch( output.name )
+ {
+ case "A==B": value = A==B; break;
+ case "A!=B": value = A!=B; break;
+ case "A>B": value = A>B; break;
+ case "A=B": value = A>=B; break;
+ }
+ this.setOutputData(i, value );
+ }
+};
+
+MathCompare.prototype.onGetOutputs = function()
+{
+ return [["A==B","boolean"],["A!=B","boolean"],["A>B","boolean"],["A=B","boolean"],["A<=B","boolean"]];
+}
+
+LiteGraph.registerNodeType("math/compare",MathCompare);
+
+function MathCondition()
+{
+ this.addInput("A","number");
+ this.addInput("B","number");
+ this.addOutput("out","boolean");
+ this.addProperty( "A", 1 );
+ this.addProperty( "B", 1 );
+ this.addProperty( "OP", ">", "string", { values: MathCondition.values } );
+
+ this.size = [60,40];
+}
+
+MathCondition.values = [">","<","==","!=","<=",">="];
+MathCondition["@OP"] = { type:"enum", title: "operation", values: MathCondition.values };
+
+MathCondition.title = "Condition";
+MathCondition.desc = "evaluates condition between A and B";
+
+MathCondition.prototype.onExecute = function()
+{
+ var A = this.getInputData(0);
+ if(A === undefined)
+ A = this.properties.A;
+ else
+ this.properties.A = A;
+
+ var B = this.getInputData(1);
+ if(B === undefined)
+ B = this.properties.B;
+ else
+ this.properties.B = B;
+
+ var result = true;
+ switch(this.properties.OP)
+ {
+ case ">": result = A>B; break;
+ case "<": result = A=": result = A>=B; break;
+ }
+
+ this.setOutputData(0, result );
+}
+
+LiteGraph.registerNodeType("math/condition", MathCondition);
+
+
+function MathAccumulate()
+{
+ this.addInput("inc","number");
+ this.addOutput("total","number");
+ this.addProperty( "increment", 1 );
+ this.addProperty( "value", 0 );
+}
+
+MathAccumulate.title = "Accumulate";
+MathAccumulate.desc = "Increments a value every time";
+
+MathAccumulate.prototype.onExecute = function()
+{
+ if(this.properties.value === null)
+ this.properties.value = 0;
+
+ var inc = this.getInputData(0);
+ if(inc !== null)
+ this.properties.value += inc;
+ else
+ this.properties.value += this.properties.increment;
+ this.setOutputData(0, this.properties.value );
+}
+
+LiteGraph.registerNodeType("math/accumulate", MathAccumulate);
+
+//Math Trigonometry
+function MathTrigonometry()
+{
+ this.addInput("v","number");
+ this.addOutput("sin","number");
+
+ this.addProperty( "amplitude", 1 );
+ this.addProperty( "offset", 0 );
+ this.bgImageUrl = "nodes/imgs/icon-sin.png";
+}
+
+MathTrigonometry.title = "Trigonometry";
+MathTrigonometry.desc = "Sin Cos Tan";
+MathTrigonometry.filter = "shader";
+
+MathTrigonometry.prototype.onExecute = function()
+{
+ var v = this.getInputData(0);
+ if(v == null)
+ v = 0;
+ var amplitude = this.properties["amplitude"];
+ var slot = this.findInputSlot("amplitude");
+ if(slot != -1)
+ amplitude = this.getInputData(slot);
+ var offset = this.properties["offset"];
+ slot = this.findInputSlot("offset");
+ if(slot != -1)
+ offset = this.getInputData(slot);
+
+ for(var i = 0, l = this.outputs.length; i < l; ++i)
+ {
+ var output = this.outputs[i];
+ switch( output.name )
+ {
+ case "sin": value = Math.sin(v); break;
+ case "cos": value = Math.cos(v); break;
+ case "tan": value = Math.tan(v); break;
+ case "asin": value = Math.asin(v); break;
+ case "acos": value = Math.acos(v); break;
+ case "atan": value = Math.atan(v); break;
+ }
+ this.setOutputData(i, amplitude * value + offset);
+ }
+}
+
+MathTrigonometry.prototype.onGetInputs = function()
+{
+ return [["v","number"],["amplitude","number"],["offset","number"]];
+}
+
+
+MathTrigonometry.prototype.onGetOutputs = function()
+{
+ return [["sin","number"],["cos","number"],["tan","number"],["asin","number"],["acos","number"],["atan","number"]];
+}
+
+
+LiteGraph.registerNodeType("math/trigonometry", MathTrigonometry );
+
+
+
+//math library for safe math operations without eval
+if(typeof(math) != undefined)
+{
+ function MathFormula()
+ {
+ this.addInputs("x","number");
+ this.addInputs("y","number");
+ this.addOutputs("","number");
+ this.properties = {x:1.0, y:1.0, formula:"x+y"};
+ }
+
+ MathFormula.title = "Formula";
+ MathFormula.desc = "Compute safe formula";
+
+ MathFormula.prototype.onExecute = function()
+ {
+ var x = this.getInputData(0);
+ var y = this.getInputData(1);
+ if(x != null)
+ this.properties["x"] = x;
+ else
+ x = this.properties["x"];
+
+ if(y!=null)
+ this.properties["y"] = y;
+ else
+ y = this.properties["y"];
+
+ var f = this.properties["formula"];
+ var value = math.eval(f,{x:x,y:y,T: this.graph.globaltime });
+ this.setOutputData(0, value );
+ }
+
+ MathFormula.prototype.onDrawBackground = function()
+ {
+ var f = this.properties["formula"];
+ this.outputs[0].label = f;
+ }
+
+ MathFormula.prototype.onGetOutputs = function()
+ {
+ return [["A-B","number"],["A*B","number"],["A/B","number"]];
+ }
+
+ LiteGraph.registerNodeType("math/formula", MathFormula );
+}
+
+
+function Math3DVec2ToXYZ()
+{
+ this.addInput("vec2","vec2");
+ this.addOutput("x","number");
+ this.addOutput("y","number");
+}
+
+Math3DVec2ToXYZ.title = "Vec2->XY";
+Math3DVec2ToXYZ.desc = "vector 2 to components";
+
+Math3DVec2ToXYZ.prototype.onExecute = function()
+{
+ var v = this.getInputData(0);
+ if(v == null) return;
+
+ this.setOutputData( 0, v[0] );
+ this.setOutputData( 1, v[1] );
+}
+
+LiteGraph.registerNodeType("math3d/vec2-to-xyz", Math3DVec2ToXYZ );
+
+
+function Math3DXYToVec2()
+{
+ this.addInputs([["x","number"],["y","number"]]);
+ this.addOutput("vec2","vec2");
+ this.properties = {x:0, y:0};
+ this._data = new Float32Array(2);
+}
+
+Math3DXYToVec2.title = "XY->Vec2";
+Math3DXYToVec2.desc = "components to vector2";
+
+Math3DXYToVec2.prototype.onExecute = function()
+{
+ var x = this.getInputData(0);
+ if(x == null) x = this.properties.x;
+ var y = this.getInputData(1);
+ if(y == null) y = this.properties.y;
+
+ var data = this._data;
+ data[0] = x;
+ data[1] = y;
+
+ this.setOutputData( 0, data );
+}
+
+LiteGraph.registerNodeType("math3d/xy-to-vec2", Math3DXYToVec2 );
+
+
+
+
+function Math3DVec3ToXYZ()
+{
+ this.addInput("vec3","vec3");
+ this.addOutput("x","number");
+ this.addOutput("y","number");
+ this.addOutput("z","number");
+}
+
+Math3DVec3ToXYZ.title = "Vec3->XYZ";
+Math3DVec3ToXYZ.desc = "vector 3 to components";
+
+Math3DVec3ToXYZ.prototype.onExecute = function()
+{
+ var v = this.getInputData(0);
+ if(v == null) return;
+
+ this.setOutputData( 0, v[0] );
+ this.setOutputData( 1, v[1] );
+ this.setOutputData( 2, v[2] );
+}
+
+LiteGraph.registerNodeType("math3d/vec3-to-xyz", Math3DVec3ToXYZ );
+
+
+function Math3DXYZToVec3()
+{
+ this.addInputs([["x","number"],["y","number"],["z","number"]]);
+ this.addOutput("vec3","vec3");
+ this.properties = {x:0, y:0, z:0};
+ this._data = new Float32Array(3);
+}
+
+Math3DXYZToVec3.title = "XYZ->Vec3";
+Math3DXYZToVec3.desc = "components to vector3";
+
+Math3DXYZToVec3.prototype.onExecute = function()
+{
+ var x = this.getInputData(0);
+ if(x == null) x = this.properties.x;
+ var y = this.getInputData(1);
+ if(y == null) y = this.properties.y;
+ var z = this.getInputData(2);
+ if(z == null) z = this.properties.z;
+
+ var data = this._data;
+ data[0] = x;
+ data[1] = y;
+ data[2] = z;
+
+ this.setOutputData( 0, data );
+}
+
+LiteGraph.registerNodeType("math3d/xyz-to-vec3", Math3DXYZToVec3 );
+
+
+
+function Math3DVec4ToXYZW()
+{
+ this.addInput("vec4","vec4");
+ this.addOutput("x","number");
+ this.addOutput("y","number");
+ this.addOutput("z","number");
+ this.addOutput("w","number");
+}
+
+Math3DVec4ToXYZW.title = "Vec4->XYZW";
+Math3DVec4ToXYZW.desc = "vector 4 to components";
+
+Math3DVec4ToXYZW.prototype.onExecute = function()
+{
+ var v = this.getInputData(0);
+ if(v == null) return;
+
+ this.setOutputData( 0, v[0] );
+ this.setOutputData( 1, v[1] );
+ this.setOutputData( 2, v[2] );
+ this.setOutputData( 3, v[3] );
+}
+
+LiteGraph.registerNodeType("math3d/vec4-to-xyzw", Math3DVec4ToXYZW );
+
+
+function Math3DXYZWToVec4()
+{
+ this.addInputs([["x","number"],["y","number"],["z","number"],["w","number"]]);
+ this.addOutput("vec4","vec4");
+ this.properties = {x:0, y:0, z:0, w:0};
+ this._data = new Float32Array(4);
+}
+
+Math3DXYZWToVec4.title = "XYZW->Vec4";
+Math3DXYZWToVec4.desc = "components to vector4";
+
+Math3DXYZWToVec4.prototype.onExecute = function()
+{
+ var x = this.getInputData(0);
+ if(x == null) x = this.properties.x;
+ var y = this.getInputData(1);
+ if(y == null) y = this.properties.y;
+ var z = this.getInputData(2);
+ if(z == null) z = this.properties.z;
+ var w = this.getInputData(3);
+ if(w == null) w = this.properties.w;
+
+ var data = this._data;
+ data[0] = x;
+ data[1] = y;
+ data[2] = z;
+ data[3] = w;
+
+ this.setOutputData( 0, data );
+}
+
+LiteGraph.registerNodeType("math3d/xyzw-to-vec4", Math3DXYZWToVec4 );
+
+
+
+
+//if glMatrix is installed...
+if(global.glMatrix)
+{
+
+ function Math3DQuaternion()
+ {
+ this.addOutput("quat","quat");
+ this.properties = { x:0, y:0, z:0, w: 1 };
+ this._value = quat.create();
+ }
+
+ Math3DQuaternion.title = "Quaternion";
+ Math3DQuaternion.desc = "quaternion";
+
+ Math3DQuaternion.prototype.onExecute = function()
+ {
+ this._value[0] = this.properties.x;
+ this._value[1] = this.properties.y;
+ this._value[2] = this.properties.z;
+ this._value[3] = this.properties.w;
+ this.setOutputData( 0, this._value );
+ }
+
+ LiteGraph.registerNodeType("math3d/quaternion", Math3DQuaternion );
+
+
+ function Math3DRotation()
+ {
+ this.addInputs([["degrees","number"],["axis","vec3"]]);
+ this.addOutput("quat","quat");
+ this.properties = { angle:90.0, axis: vec3.fromValues(0,1,0) };
+
+ this._value = quat.create();
+ }
+
+ Math3DRotation.title = "Rotation";
+ Math3DRotation.desc = "quaternion rotation";
+
+ Math3DRotation.prototype.onExecute = function()
+ {
+ var angle = this.getInputData(0);
+ if(angle == null) angle = this.properties.angle;
+ var axis = this.getInputData(1);
+ if(axis == null) axis = this.properties.axis;
+
+ var R = quat.setAxisAngle( this._value, axis, angle * 0.0174532925 );
+ this.setOutputData( 0, R );
+ }
+
+
+ LiteGraph.registerNodeType("math3d/rotation", Math3DRotation );
+
+
+ //Math3D rotate vec3
+ function Math3DRotateVec3()
+ {
+ this.addInputs([["vec3","vec3"],["quat","quat"]]);
+ this.addOutput("result","vec3");
+ this.properties = { vec: [0,0,1] };
+ }
+
+ Math3DRotateVec3.title = "Rot. Vec3";
+ Math3DRotateVec3.desc = "rotate a point";
+
+ Math3DRotateVec3.prototype.onExecute = function()
+ {
+ var vec = this.getInputData(0);
+ if(vec == null) vec = this.properties.vec;
+ var quat = this.getInputData(1);
+ if(quat == null)
+ this.setOutputData(vec);
+ else
+ this.setOutputData( 0, vec3.transformQuat( vec3.create(), vec, quat ) );
+ }
+
+ LiteGraph.registerNodeType("math3d/rotate_vec3", Math3DRotateVec3);
+
+
+
+ function Math3DMultQuat()
+ {
+ this.addInputs( [["A","quat"],["B","quat"]] );
+ this.addOutput( "A*B","quat" );
+
+ this._value = quat.create();
+ }
+
+ Math3DMultQuat.title = "Mult. Quat";
+ Math3DMultQuat.desc = "rotate quaternion";
+
+ Math3DMultQuat.prototype.onExecute = function()
+ {
+ var A = this.getInputData(0);
+ if(A == null) return;
+ var B = this.getInputData(1);
+ if(B == null) return;
+
+ var R = quat.multiply( this._value, A, B );
+ this.setOutputData( 0, R );
+ }
+
+ LiteGraph.registerNodeType("math3d/mult-quat", Math3DMultQuat );
+
+
+ function Math3DQuatSlerp()
+ {
+ this.addInputs( [["A","quat"],["B","quat"],["factor","number"]] );
+ this.addOutput( "slerp","quat" );
+ this.addProperty( "factor", 0.5 );
+
+ this._value = quat.create();
+ }
+
+ Math3DQuatSlerp.title = "Quat Slerp";
+ Math3DQuatSlerp.desc = "quaternion spherical interpolation";
+
+ Math3DQuatSlerp.prototype.onExecute = function()
+ {
+ var A = this.getInputData(0);
+ if(A == null)
+ return;
+ var B = this.getInputData(1);
+ if(B == null)
+ return;
+ var factor = this.properties.factor;
+ if( this.getInputData(2) != null )
+ factor = this.getInputData(2);
+
+ var R = quat.slerp( this._value, A, B, factor );
+ this.setOutputData( 0, R );
+ }
+
+ LiteGraph.registerNodeType("math3d/quat-slerp", Math3DQuatSlerp );
+
+} //glMatrix
+
})(this);
(function(global){
var LiteGraph = global.LiteGraph;
@@ -11504,6163 +12132,6163 @@ LiteGraph.registerNodeType("graphics/webcam", ImageWebcam );
})(this);
-(function(global){
-var LiteGraph = global.LiteGraph;
-
-//Works with Litegl.js to create WebGL nodes
-global.LGraphTexture = null;
-
-if(typeof(GL) != "undefined")
-{
- function LGraphTexture()
- {
- this.addOutput("Texture","Texture");
- this.properties = { name:"", filter: true };
- this.size = [LGraphTexture.image_preview_size, LGraphTexture.image_preview_size];
- }
-
- global.LGraphTexture = LGraphTexture;
-
- LGraphTexture.title = "Texture";
- LGraphTexture.desc = "Texture";
- LGraphTexture.widgets_info = {"name": { widget:"texture"}, "filter": { widget:"checkbox"} };
-
- //REPLACE THIS TO INTEGRATE WITH YOUR FRAMEWORK
- LGraphTexture.loadTextureCallback = null; //function in charge of loading textures when not present in the container
- LGraphTexture.image_preview_size = 256;
-
- //flags to choose output texture type
- LGraphTexture.PASS_THROUGH = 1; //do not apply FX
- LGraphTexture.COPY = 2; //create new texture with the same properties as the origin texture
- LGraphTexture.LOW = 3; //create new texture with low precision (byte)
- LGraphTexture.HIGH = 4; //create new texture with high precision (half-float)
- LGraphTexture.REUSE = 5; //reuse input texture
- LGraphTexture.DEFAULT = 2;
-
- LGraphTexture.MODE_VALUES = {
- "pass through": LGraphTexture.PASS_THROUGH,
- "copy": LGraphTexture.COPY,
- "low": LGraphTexture.LOW,
- "high": LGraphTexture.HIGH,
- "reuse": LGraphTexture.REUSE,
- "default": LGraphTexture.DEFAULT
- };
-
- //returns the container where all the loaded textures are stored (overwrite if you have a Resources Manager)
- LGraphTexture.getTexturesContainer = function()
- {
- return gl.textures;
- }
-
- //process the loading of a texture (overwrite it if you have a Resources Manager)
- LGraphTexture.loadTexture = function(name, options)
- {
- options = options || {};
- var url = name;
- if(url.substr(0,7) == "http://")
- {
- if(LiteGraph.proxy) //proxy external files
- url = LiteGraph.proxy + url.substr(7);
- }
-
- var container = LGraphTexture.getTexturesContainer();
- var tex = container[ name ] = GL.Texture.fromURL(url, options);
- return tex;
- }
-
- LGraphTexture.getTexture = function(name)
- {
- var container = this.getTexturesContainer();
-
- if(!container)
- throw("Cannot load texture, container of textures not found");
-
- var tex = container[ name ];
- if(!tex && name && name[0] != ":")
- return this.loadTexture(name);
-
- return tex;
- }
-
- //used to compute the appropiate output texture
- LGraphTexture.getTargetTexture = function( origin, target, mode )
- {
- if(!origin)
- throw("LGraphTexture.getTargetTexture expects a reference texture");
-
- var tex_type = null;
-
- switch(mode)
- {
- case LGraphTexture.LOW: tex_type = gl.UNSIGNED_BYTE; break;
- case LGraphTexture.HIGH: tex_type = gl.HIGH_PRECISION_FORMAT; break;
- case LGraphTexture.REUSE: return origin; break;
- case LGraphTexture.COPY:
- default: tex_type = origin ? origin.type : gl.UNSIGNED_BYTE; break;
- }
-
- if(!target || target.width != origin.width || target.height != origin.height || target.type != tex_type )
- target = new GL.Texture( origin.width, origin.height, { type: tex_type, format: gl.RGBA, filter: gl.LINEAR });
-
- return target;
- }
-
-
- LGraphTexture.getTextureType = function( precision, ref_texture )
- {
- var type = ref_texture ? ref_texture.type : gl.UNSIGNED_BYTE;
- switch( precision )
- {
- case LGraphTexture.HIGH: type = gl.HIGH_PRECISION_FORMAT; break;
- case LGraphTexture.LOW: type = gl.UNSIGNED_BYTE; break;
- //no default
- }
- return type;
- }
-
- LGraphTexture.getNoiseTexture = function()
- {
- if(this._noise_texture)
- return this._noise_texture;
-
- var noise = new Uint8Array(512*512*4);
- for(var i = 0; i < 512*512*4; ++i)
- noise[i] = Math.random() * 255;
-
- var texture = GL.Texture.fromMemory(512,512,noise,{ format: gl.RGBA, wrap: gl.REPEAT, filter: gl.NEAREST });
- this._noise_texture = texture;
- return texture;
- }
-
- LGraphTexture.prototype.onDropFile = function(data, filename, file)
- {
- if(!data)
- {
- this._drop_texture = null;
- this.properties.name = "";
- }
- else
- {
- var texture = null;
- if( typeof(data) == "string" )
- texture = GL.Texture.fromURL( data );
- else if( filename.toLowerCase().indexOf(".dds") != -1 )
- texture = GL.Texture.fromDDSInMemory(data);
- else
- {
- var blob = new Blob([file]);
- var url = URL.createObjectURL(blob);
- texture = GL.Texture.fromURL( url );
- }
-
- this._drop_texture = texture;
- this.properties.name = filename;
- }
- }
-
- LGraphTexture.prototype.getExtraMenuOptions = function(graphcanvas)
- {
- var that = this;
- if(!this._drop_texture)
- return;
- return [ {content:"Clear", callback:
- function() {
- that._drop_texture = null;
- that.properties.name = "";
- }
- }];
- }
-
- LGraphTexture.prototype.onExecute = function()
- {
- var tex = null;
- if(this.isOutputConnected(1))
- tex = this.getInputData(0);
-
- if(!tex && this._drop_texture)
- tex = this._drop_texture;
-
- if(!tex && this.properties.name)
- tex = LGraphTexture.getTexture( this.properties.name );
-
- if(!tex)
- return;
-
- this._last_tex = tex;
-
- if(this.properties.filter === false)
- tex.setParameter( gl.TEXTURE_MAG_FILTER, gl.NEAREST );
- else
- tex.setParameter( gl.TEXTURE_MAG_FILTER, gl.LINEAR );
-
- this.setOutputData(0, tex);
-
- for(var i = 1; i < this.outputs.length; i++)
- {
- var output = this.outputs[i];
- if(!output)
- continue;
- var v = null;
- if(output.name == "width")
- v = tex.width;
- else if(output.name == "height")
- v = tex.height;
- else if(output.name == "aspect")
- v = tex.width / tex.height;
- this.setOutputData(i, v);
- }
- }
-
- LGraphTexture.prototype.onResourceRenamed = function(old_name,new_name)
- {
- if(this.properties.name == old_name)
- this.properties.name = new_name;
- }
-
- LGraphTexture.prototype.onDrawBackground = function(ctx)
- {
- if( this.flags.collapsed || this.size[1] <= 20 )
- return;
-
- if( this._drop_texture && ctx.webgl )
- {
- ctx.drawImage( this._drop_texture, 0,0,this.size[0],this.size[1]);
- //this._drop_texture.renderQuad(this.pos[0],this.pos[1],this.size[0],this.size[1]);
- return;
- }
-
-
- //Different texture? then get it from the GPU
- if(this._last_preview_tex != this._last_tex)
- {
- if(ctx.webgl)
- {
- this._canvas = this._last_tex;
- }
- else
- {
- var tex_canvas = LGraphTexture.generateLowResTexturePreview(this._last_tex);
- if(!tex_canvas)
- return;
-
- this._last_preview_tex = this._last_tex;
- this._canvas = cloneCanvas(tex_canvas);
- }
- }
-
- if(!this._canvas)
- return;
-
- //render to graph canvas
- ctx.save();
- if(!ctx.webgl) //reverse image
- {
- ctx.translate(0,this.size[1]);
- ctx.scale(1,-1);
- }
- ctx.drawImage(this._canvas,0,0,this.size[0],this.size[1]);
- ctx.restore();
- }
-
-
- //very slow, used at your own risk
- LGraphTexture.generateLowResTexturePreview = function(tex)
- {
- if(!tex)
- return null;
-
- var size = LGraphTexture.image_preview_size;
- var temp_tex = tex;
-
- if(tex.format == gl.DEPTH_COMPONENT)
- return null; //cannot generate from depth
-
- //Generate low-level version in the GPU to speed up
- if(tex.width > size || tex.height > size)
- {
- temp_tex = this._preview_temp_tex;
- if(!this._preview_temp_tex)
- {
- temp_tex = new GL.Texture(size,size, { minFilter: gl.NEAREST });
- this._preview_temp_tex = temp_tex;
- }
-
- //copy
- tex.copyTo(temp_tex);
- tex = temp_tex;
- }
-
- //create intermediate canvas with lowquality version
- var tex_canvas = this._preview_canvas;
- if(!tex_canvas)
- {
- tex_canvas = createCanvas(size,size);
- this._preview_canvas = tex_canvas;
- }
-
- if(temp_tex)
- temp_tex.toCanvas(tex_canvas);
- return tex_canvas;
- }
-
- LGraphTexture.prototype.getResources = function(res)
- {
- res[ this.properties.name ] = GL.Texture;
- return res;
- }
-
- LGraphTexture.prototype.onGetInputs = function()
- {
- return [["in","Texture"]];
- }
-
-
- LGraphTexture.prototype.onGetOutputs = function()
- {
- return [["width","number"],["height","number"],["aspect","number"]];
- }
-
- LiteGraph.registerNodeType("texture/texture", LGraphTexture );
-
- //**************************
- function LGraphTexturePreview()
- {
- this.addInput("Texture","Texture");
- this.properties = { flipY: false };
- this.size = [LGraphTexture.image_preview_size, LGraphTexture.image_preview_size];
- }
-
- LGraphTexturePreview.title = "Preview";
- LGraphTexturePreview.desc = "Show a texture in the graph canvas";
- LGraphTexturePreview.allow_preview = false;
-
- LGraphTexturePreview.prototype.onDrawBackground = function(ctx)
- {
- if(this.flags.collapsed)
- return;
-
- if(!ctx.webgl && !LGraphTexturePreview.allow_preview)
- return; //not working well
-
- var tex = this.getInputData(0);
- if(!tex)
- return;
-
- var tex_canvas = null;
-
- if(!tex.handle && ctx.webgl)
- tex_canvas = tex;
- else
- tex_canvas = LGraphTexture.generateLowResTexturePreview(tex);
-
- //render to graph canvas
- ctx.save();
- if(this.properties.flipY)
- {
- ctx.translate(0,this.size[1]);
- ctx.scale(1,-1);
- }
- ctx.drawImage(tex_canvas,0,0,this.size[0],this.size[1]);
- ctx.restore();
- }
-
- LiteGraph.registerNodeType("texture/preview", LGraphTexturePreview );
-
- //**************************************
-
- function LGraphTextureSave()
- {
- this.addInput("Texture","Texture");
- this.addOutput("","Texture");
- this.properties = {name:""};
- }
-
- LGraphTextureSave.title = "Save";
- LGraphTextureSave.desc = "Save a texture in the repository";
-
- LGraphTextureSave.prototype.onExecute = function()
- {
- var tex = this.getInputData(0);
- if(!tex)
- return;
-
- if(this.properties.name)
- {
- //for cases where we want to perform something when storing it
- if( LGraphTexture.storeTexture )
- LGraphTexture.storeTexture( this.properties.name, tex );
- else
- {
- var container = LGraphTexture.getTexturesContainer();
- container[ this.properties.name ] = tex;
- }
- }
-
- this.setOutputData(0, tex);
- }
-
- LiteGraph.registerNodeType("texture/save", LGraphTextureSave );
-
- //****************************************************
-
- function LGraphTextureOperation()
- {
- this.addInput("Texture","Texture");
- this.addInput("TextureB","Texture");
- this.addInput("value","number");
- this.addOutput("Texture","Texture");
- this.help = "
pixelcode must be vec3
\
- uvcode must be vec2, is optional
\
- uv: tex. coords
color: texture
colorB: textureB
time: scene time
value: input value
";
-
- this.properties = {value:1, uvcode:"", pixelcode:"color + colorB * value", precision: LGraphTexture.DEFAULT };
- }
-
- LGraphTextureOperation.widgets_info = {
- "uvcode": { widget:"textarea", height: 100 },
- "pixelcode": { widget:"textarea", height: 100 },
- "precision": { widget:"combo", values: LGraphTexture.MODE_VALUES }
- };
-
- LGraphTextureOperation.title = "Operation";
- LGraphTextureOperation.desc = "Texture shader operation";
-
- LGraphTextureOperation.prototype.getExtraMenuOptions = function(graphcanvas)
- {
- var that = this;
- var txt = !that.properties.show ? "Show Texture" : "Hide Texture";
- return [ {content: txt, callback:
- function() {
- that.properties.show = !that.properties.show;
- }
- }];
- }
-
- LGraphTextureOperation.prototype.onDrawBackground = function(ctx)
- {
- if(this.flags.collapsed || this.size[1] <= 20 || !this.properties.show)
- return;
-
- if(!this._tex)
- return;
-
- //only works if using a webgl renderer
- if(this._tex.gl != ctx)
- return;
-
- //render to graph canvas
- ctx.save();
- ctx.drawImage(this._tex, 0, 0, this.size[0], this.size[1]);
- ctx.restore();
- }
-
- LGraphTextureOperation.prototype.onExecute = function()
- {
- var tex = this.getInputData(0);
-
- if(!this.isOutputConnected(0))
- return; //saves work
-
- if(this.properties.precision === LGraphTexture.PASS_THROUGH)
- {
- this.setOutputData(0, tex);
- return;
- }
-
- var texB = this.getInputData(1);
-
- if(!this.properties.uvcode && !this.properties.pixelcode)
- return;
-
- var width = 512;
- var height = 512;
- if(tex)
- {
- width = tex.width;
- height = tex.height;
- }
- else if (texB)
- {
- width = texB.width;
- height = texB.height;
- }
-
- var type = LGraphTexture.getTextureType( this.properties.precision, tex );
-
- if(!tex && !this._tex )
- this._tex = new GL.Texture( width, height, { type: type, format: gl.RGBA, filter: gl.LINEAR });
- else
- this._tex = LGraphTexture.getTargetTexture( tex || this._tex, this._tex, this.properties.precision );
-
- var uvcode = "";
- if(this.properties.uvcode)
- {
- uvcode = "uv = " + this.properties.uvcode;
- if(this.properties.uvcode.indexOf(";") != -1) //there are line breaks, means multiline code
- uvcode = this.properties.uvcode;
- }
-
- var pixelcode = "";
- if(this.properties.pixelcode)
- {
- pixelcode = "result = " + this.properties.pixelcode;
- if(this.properties.pixelcode.indexOf(";") != -1) //there are line breaks, means multiline code
- pixelcode = this.properties.pixelcode;
- }
-
- var shader = this._shader;
-
- if(!shader || this._shader_code != (uvcode + "|" + pixelcode) )
- {
- try
- {
- this._shader = new GL.Shader(Shader.SCREEN_VERTEX_SHADER, LGraphTextureOperation.pixel_shader, { UV_CODE: uvcode, PIXEL_CODE: pixelcode });
- this.boxcolor = "#00FF00";
- }
- catch (err)
- {
- console.log("Error compiling shader: ", err);
- this.boxcolor = "#FF0000";
- return;
- }
- this.boxcolor = "#FF0000";
-
- this._shader_code = (uvcode + "|" + pixelcode);
- shader = this._shader;
- }
-
- if(!shader)
- {
- this.boxcolor = "red";
- return;
- }
- else
- this.boxcolor = "green";
-
- var value = this.getInputData(2);
- if(value != null)
- this.properties.value = value;
- else
- value = parseFloat( this.properties.value );
-
- var time = this.graph.getTime();
-
- this._tex.drawTo(function() {
- gl.disable( gl.DEPTH_TEST );
- gl.disable( gl.CULL_FACE );
- gl.disable( gl.BLEND );
- if(tex) tex.bind(0);
- if(texB) texB.bind(1);
- var mesh = Mesh.getScreenQuad();
- shader.uniforms({u_texture:0, u_textureB:1, value: value, texSize:[width,height], time: time}).draw(mesh);
- });
-
- this.setOutputData(0, this._tex);
- }
-
- LGraphTextureOperation.pixel_shader = "precision highp float;\n\
- \n\
- uniform sampler2D u_texture;\n\
- uniform sampler2D u_textureB;\n\
- varying vec2 v_coord;\n\
- uniform vec2 texSize;\n\
- uniform float time;\n\
- uniform float value;\n\
- \n\
- void main() {\n\
- vec2 uv = v_coord;\n\
- UV_CODE;\n\
- vec4 color4 = texture2D(u_texture, uv);\n\
- vec3 color = color4.rgb;\n\
- vec4 color4B = texture2D(u_textureB, uv);\n\
- vec3 colorB = color4B.rgb;\n\
- vec3 result = color;\n\
- float alpha = 1.0;\n\
- PIXEL_CODE;\n\
- gl_FragColor = vec4(result, alpha);\n\
- }\n\
- ";
-
- LiteGraph.registerNodeType("texture/operation", LGraphTextureOperation );
-
- //****************************************************
-
- function LGraphTextureShader()
- {
- this.addOutput("out","Texture");
- this.properties = {code:"", width: 512, height: 512, precision: LGraphTexture.DEFAULT };
-
- this.properties.code = "\nvoid main() {\n vec2 uv = v_coord;\n vec3 color = vec3(0.0);\n//your code here\n\ngl_FragColor = vec4(color, 1.0);\n}\n";
- this._uniforms = { in_texture:0, texSize: vec2.create(), time: 0 };
- }
-
- LGraphTextureShader.title = "Shader";
- LGraphTextureShader.desc = "Texture shader";
- LGraphTextureShader.widgets_info = {
- "code": { type:"code" },
- "precision": { widget:"combo", values: LGraphTexture.MODE_VALUES }
- };
-
- LGraphTextureShader.prototype.onPropertyChanged = function(name, value)
- {
- if(name != "code")
- return;
-
- var shader = this.getShader();
- if(!shader)
- return;
-
- //update connections
- var uniforms = shader.uniformInfo;
-
- //remove deprecated slots
- if(this.inputs)
- {
- var already = {};
- for(var i = 0; i < this.inputs.length; ++i)
- {
- var info = this.getInputInfo(i);
- if(!info)
- continue;
-
- if( uniforms[ info.name ] && !already[ info.name ] )
- {
- already[ info.name ] = true;
- continue;
- }
- this.removeInput(i);
- i--;
- }
- }
-
- //update existing ones
- for(var i in uniforms)
- {
- var info = shader.uniformInfo[i];
- if(info.loc === null)
- continue; //is an attribute, not a uniform
- if(i == "time") //default one
- continue;
-
- var type = "number";
- if( this._shader.samplers[i] )
- type = "texture";
- else
- {
- switch(info.size)
- {
- case 1: type = "number"; break;
- case 2: type = "vec2"; break;
- case 3: type = "vec3"; break;
- case 4: type = "vec4"; break;
- case 9: type = "mat3"; break;
- case 16: type = "mat4"; break;
- default: continue;
- }
- }
-
- var slot = this.findInputSlot(i);
- if(slot == -1)
- {
- this.addInput(i,type);
- continue;
- }
-
- var input_info = this.getInputInfo(slot);
- if(!input_info)
- this.addInput(i,type);
- else
- {
- if(input_info.type == type)
- continue;
- this.removeInput(slot,type);
- this.addInput(i,type);
- }
- }
- }
-
- LGraphTextureShader.prototype.getShader = function()
- {
- //replug
- if(this._shader && this._shader_code == this.properties.code)
- return this._shader;
-
- this._shader_code = this.properties.code;
- this._shader = new GL.Shader(Shader.SCREEN_VERTEX_SHADER, LGraphTextureShader.pixel_shader + this.properties.code );
- if(!this._shader) {
- this.boxcolor = "red";
- return null;
- }
- else
- this.boxcolor = "green";
- return this._shader;
- }
-
- LGraphTextureShader.prototype.onExecute = function()
- {
- if(!this.isOutputConnected(0))
- return; //saves work
-
- var shader = this.getShader();
- if(!shader)
- return;
-
- var tex_slot = 0;
- var in_tex = null;
-
- //set uniforms
- for(var i = 0; i < this.inputs.length; ++i)
- {
- var info = this.getInputInfo(i);
- var data = this.getInputData(i);
- if(data == null)
- continue;
-
- if(data.constructor === GL.Texture)
- {
- data.bind(tex_slot);
- if(!in_tex)
- in_tex = data;
- data = tex_slot;
- tex_slot++;
- }
- shader.setUniform( info.name, data ); //data is tex_slot
- }
-
- var uniforms = this._uniforms;
- var type = LGraphTexture.getTextureType( this.properties.precision, in_tex );
-
- //render to texture
- var w = this.properties.width|0;
- var h = this.properties.height|0;
- if(w == 0)
- w = in_tex ? in_tex.width : gl.canvas.width;
- if(h == 0)
- h = in_tex ? in_tex.height : gl.canvas.height;
- uniforms.texSize[0] = w;
- uniforms.texSize[1] = h;
- uniforms.time = this.graph.getTime();
-
- if(!this._tex || this._tex.type != type || this._tex.width != w || this._tex.height != h )
- this._tex = new GL.Texture( w, h, { type: type, format: gl.RGBA, filter: gl.LINEAR });
- var tex = this._tex;
- tex.drawTo(function() {
- shader.uniforms( uniforms ).draw( GL.Mesh.getScreenQuad() );
- });
-
- this.setOutputData( 0, this._tex );
- }
-
- LGraphTextureShader.pixel_shader = "precision highp float;\n\
- \n\
- varying vec2 v_coord;\n\
- uniform float time;\n\
- ";
-
- LiteGraph.registerNodeType("texture/shader", LGraphTextureShader );
-
- // Texture Scale Offset
-
- function LGraphTextureScaleOffset()
- {
- this.addInput("in","Texture");
- this.addInput("scale","vec2");
- this.addInput("offset","vec2");
- this.addOutput("out","Texture");
- this.properties = { offset: vec2.fromValues(0,0), scale: vec2.fromValues(1,1), precision: LGraphTexture.DEFAULT };
- }
-
- LGraphTextureScaleOffset.widgets_info = {
- "precision": { widget:"combo", values: LGraphTexture.MODE_VALUES }
- };
-
- LGraphTextureScaleOffset.title = "Scale/Offset";
- LGraphTextureScaleOffset.desc = "Applies an scaling and offseting";
-
- LGraphTextureScaleOffset.prototype.onExecute = function()
- {
- var tex = this.getInputData(0);
-
- if(!this.isOutputConnected(0) || !tex)
- return; //saves work
-
- if(this.properties.precision === LGraphTexture.PASS_THROUGH)
- {
- this.setOutputData(0, tex);
- return;
- }
-
- var width = tex.width;
- var height = tex.height;
- var type = this.precision === LGraphTexture.LOW ? gl.UNSIGNED_BYTE : gl.HIGH_PRECISION_FORMAT;
- if (this.precision === LGraphTexture.DEFAULT)
- type = tex.type;
-
- if(!this._tex || this._tex.width != width || this._tex.height != height || this._tex.type != type )
- this._tex = new GL.Texture( width, height, { type: type, format: gl.RGBA, filter: gl.LINEAR });
-
- var shader = this._shader;
-
- if(!shader)
- shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureScaleOffset.pixel_shader );
-
- var scale = this.getInputData(1);
- if(scale)
- {
- this.properties.scale[0] = scale[0];
- this.properties.scale[1] = scale[1];
- }
- else
- scale = this.properties.scale;
-
- var offset = this.getInputData(2);
- if(offset)
- {
- this.properties.offset[0] = offset[0];
- this.properties.offset[1] = offset[1];
- }
- else
- offset = this.properties.offset;
-
- this._tex.drawTo(function() {
- gl.disable( gl.DEPTH_TEST );
- gl.disable( gl.CULL_FACE );
- gl.disable( gl.BLEND );
- tex.bind(0);
- var mesh = Mesh.getScreenQuad();
- shader.uniforms({u_texture:0, u_scale: scale, u_offset: offset}).draw( mesh );
- });
-
- this.setOutputData( 0, this._tex );
- }
-
- LGraphTextureScaleOffset.pixel_shader = "precision highp float;\n\
- \n\
- uniform sampler2D u_texture;\n\
- uniform sampler2D u_textureB;\n\
- varying vec2 v_coord;\n\
- uniform vec2 u_scale;\n\
- uniform vec2 u_offset;\n\
- \n\
- void main() {\n\
- vec2 uv = v_coord;\n\
- uv = uv / u_scale - u_offset;\n\
- gl_FragColor = texture2D(u_texture, uv);\n\
- }\n\
- ";
-
- LiteGraph.registerNodeType("texture/scaleOffset", LGraphTextureScaleOffset );
-
-
-
- // Warp (distort a texture) *************************
-
- function LGraphTextureWarp()
- {
- this.addInput("in","Texture");
- this.addInput("warp","Texture");
- this.addInput("factor","number");
- this.addOutput("out","Texture");
- this.properties = { factor: 0.01, precision: LGraphTexture.DEFAULT };
- }
-
- LGraphTextureWarp.widgets_info = {
- "precision": { widget:"combo", values: LGraphTexture.MODE_VALUES }
- };
-
- LGraphTextureWarp.title = "Warp";
- LGraphTextureWarp.desc = "Texture warp operation";
-
- LGraphTextureWarp.prototype.onExecute = function()
- {
- var tex = this.getInputData(0);
-
- if(!this.isOutputConnected(0))
- return; //saves work
-
- if(this.properties.precision === LGraphTexture.PASS_THROUGH)
- {
- this.setOutputData(0, tex);
- return;
- }
-
- var texB = this.getInputData(1);
-
- var width = 512;
- var height = 512;
- var type = gl.UNSIGNED_BYTE;
- if(tex)
- {
- width = tex.width;
- height = tex.height;
- type = tex.type;
- }
- else if (texB)
- {
- width = texB.width;
- height = texB.height;
- type = texB.type;
- }
-
- if(!tex && !this._tex )
- this._tex = new GL.Texture( width, height, { type: this.precision === LGraphTexture.LOW ? gl.UNSIGNED_BYTE : gl.HIGH_PRECISION_FORMAT, format: gl.RGBA, filter: gl.LINEAR });
- else
- this._tex = LGraphTexture.getTargetTexture( tex || this._tex, this._tex, this.properties.precision );
-
- var shader = this._shader;
-
- if(!shader)
- shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureWarp.pixel_shader );
-
- var factor = this.getInputData(2);
- if(factor != null)
- this.properties.factor = factor;
- else
- factor = parseFloat( this.properties.factor );
-
- this._tex.drawTo(function() {
- gl.disable( gl.DEPTH_TEST );
- gl.disable( gl.CULL_FACE );
- gl.disable( gl.BLEND );
- if(tex) tex.bind(0);
- if(texB) texB.bind(1);
- var mesh = Mesh.getScreenQuad();
- shader.uniforms({u_texture:0, u_textureB:1, u_factor: factor }).draw( mesh );
- });
-
- this.setOutputData(0, this._tex);
- }
-
- LGraphTextureWarp.pixel_shader = "precision highp float;\n\
- \n\
- uniform sampler2D u_texture;\n\
- uniform sampler2D u_textureB;\n\
- varying vec2 v_coord;\n\
- uniform float u_factor;\n\
- \n\
- void main() {\n\
- vec2 uv = v_coord;\n\
- uv += ( texture2D(u_textureB, uv).rg - vec2(0.5)) * u_factor;\n\
- gl_FragColor = texture2D(u_texture, uv);\n\
- }\n\
- ";
-
- LiteGraph.registerNodeType("texture/warp", LGraphTextureWarp );
-
- //****************************************************
-
- // Texture to Viewport *****************************************
- function LGraphTextureToViewport()
- {
- this.addInput("Texture","Texture");
- this.properties = { additive: false, antialiasing: false, filter: true, disable_alpha: false, gamma: 1.0 };
- this.size[0] = 130;
- }
-
- LGraphTextureToViewport.title = "to Viewport";
- LGraphTextureToViewport.desc = "Texture to viewport";
-
- LGraphTextureToViewport.prototype.onExecute = function()
- {
- var tex = this.getInputData(0);
- if(!tex)
- return;
-
- if(this.properties.disable_alpha)
- gl.disable( gl.BLEND );
- else
- {
- gl.enable( gl.BLEND );
- if(this.properties.additive)
- gl.blendFunc( gl.SRC_ALPHA, gl.ONE );
- else
- gl.blendFunc( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA );
- }
-
- gl.disable( gl.DEPTH_TEST );
- var gamma = this.properties.gamma || 1.0;
- if( this.isInputConnected(1) )
- gamma = this.getInputData(1);
-
- tex.setParameter( gl.TEXTURE_MAG_FILTER, this.properties.filter ? gl.LINEAR : gl.NEAREST );
-
- if(this.properties.antialiasing)
- {
- if(!LGraphTextureToViewport._shader)
- LGraphTextureToViewport._shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureToViewport.aa_pixel_shader );
-
- var viewport = gl.getViewport(); //gl.getParameter(gl.VIEWPORT);
- var mesh = Mesh.getScreenQuad();
- tex.bind(0);
- LGraphTextureToViewport._shader.uniforms({u_texture:0, uViewportSize:[tex.width,tex.height], u_igamma: 1 / gamma, inverseVP: [1/tex.width,1/tex.height] }).draw(mesh);
- }
- else
- {
- if(gamma != 1.0)
- {
- if(!LGraphTextureToViewport._gamma_shader)
- LGraphTextureToViewport._gamma_shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphTextureToViewport.gamma_pixel_shader );
- tex.toViewport(LGraphTextureToViewport._gamma_shader, { u_texture:0, u_igamma: 1 / gamma });
- }
- else
- tex.toViewport();
- }
- }
-
- LGraphTextureToViewport.prototype.onGetInputs = function()
- {
- return [["gamma","number"]];
- }
-
- LGraphTextureToViewport.aa_pixel_shader = "precision highp float;\n\
- precision highp float;\n\
- varying vec2 v_coord;\n\
- uniform sampler2D u_texture;\n\
- uniform vec2 uViewportSize;\n\
- uniform vec2 inverseVP;\n\
- uniform float u_igamma;\n\
- #define FXAA_REDUCE_MIN (1.0/ 128.0)\n\
- #define FXAA_REDUCE_MUL (1.0 / 8.0)\n\
- #define FXAA_SPAN_MAX 8.0\n\
- \n\
- /* from mitsuhiko/webgl-meincraft based on the code on geeks3d.com */\n\
- vec4 applyFXAA(sampler2D tex, vec2 fragCoord)\n\
- {\n\
- vec4 color = vec4(0.0);\n\
- /*vec2 inverseVP = vec2(1.0 / uViewportSize.x, 1.0 / uViewportSize.y);*/\n\
- vec3 rgbNW = texture2D(tex, (fragCoord + vec2(-1.0, -1.0)) * inverseVP).xyz;\n\
- vec3 rgbNE = texture2D(tex, (fragCoord + vec2(1.0, -1.0)) * inverseVP).xyz;\n\
- vec3 rgbSW = texture2D(tex, (fragCoord + vec2(-1.0, 1.0)) * inverseVP).xyz;\n\
- vec3 rgbSE = texture2D(tex, (fragCoord + vec2(1.0, 1.0)) * inverseVP).xyz;\n\
- vec3 rgbM = texture2D(tex, fragCoord * inverseVP).xyz;\n\
- vec3 luma = vec3(0.299, 0.587, 0.114);\n\
- float lumaNW = dot(rgbNW, luma);\n\
- float lumaNE = dot(rgbNE, luma);\n\
- float lumaSW = dot(rgbSW, luma);\n\
- float lumaSE = dot(rgbSE, luma);\n\
- float lumaM = dot(rgbM, luma);\n\
- float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE)));\n\
- float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE)));\n\
- \n\
- vec2 dir;\n\
- dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE));\n\
- dir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE));\n\
- \n\
- float dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) * (0.25 * FXAA_REDUCE_MUL), FXAA_REDUCE_MIN);\n\
- \n\
- float rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce);\n\
- dir = min(vec2(FXAA_SPAN_MAX, FXAA_SPAN_MAX), max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX), dir * rcpDirMin)) * inverseVP;\n\
- \n\
- vec3 rgbA = 0.5 * (texture2D(tex, fragCoord * inverseVP + dir * (1.0 / 3.0 - 0.5)).xyz + \n\
- texture2D(tex, fragCoord * inverseVP + dir * (2.0 / 3.0 - 0.5)).xyz);\n\
- vec3 rgbB = rgbA * 0.5 + 0.25 * (texture2D(tex, fragCoord * inverseVP + dir * -0.5).xyz + \n\
- texture2D(tex, fragCoord * inverseVP + dir * 0.5).xyz);\n\
- \n\
- //return vec4(rgbA,1.0);\n\
- float lumaB = dot(rgbB, luma);\n\
- if ((lumaB < lumaMin) || (lumaB > lumaMax))\n\
- color = vec4(rgbA, 1.0);\n\
- else\n\
- color = vec4(rgbB, 1.0);\n\
- if(u_igamma != 1.0)\n\
- color.xyz = pow( color.xyz, vec3(u_igamma) );\n\
- return color;\n\
- }\n\
- \n\
- void main() {\n\
- gl_FragColor = applyFXAA( u_texture, v_coord * uViewportSize) ;\n\
- }\n\
- ";
-
- LGraphTextureToViewport.gamma_pixel_shader = "precision highp float;\n\
- precision highp float;\n\
- varying vec2 v_coord;\n\
- uniform sampler2D u_texture;\n\
- uniform float u_igamma;\n\
- void main() {\n\
- vec4 color = texture2D( u_texture, v_coord);\n\
- color.xyz = pow(color.xyz, vec3(u_igamma) );\n\
- gl_FragColor = color;\n\
- }\n\
- ";
-
-
- LiteGraph.registerNodeType("texture/toviewport", LGraphTextureToViewport );
-
-
- // Texture Copy *****************************************
- function LGraphTextureCopy()
- {
- this.addInput("Texture","Texture");
- this.addOutput("","Texture");
- this.properties = { size: 0, generate_mipmaps: false, precision: LGraphTexture.DEFAULT };
- }
-
- LGraphTextureCopy.title = "Copy";
- LGraphTextureCopy.desc = "Copy Texture";
- LGraphTextureCopy.widgets_info = {
- size: { widget:"combo", values:[0,32,64,128,256,512,1024,2048]},
- precision: { widget:"combo", values: LGraphTexture.MODE_VALUES }
- };
-
- LGraphTextureCopy.prototype.onExecute = function()
- {
- var tex = this.getInputData(0);
- if(!tex && !this._temp_texture)
- return;
-
- if(!this.isOutputConnected(0))
- return; //saves work
-
- //copy the texture
- if(tex)
- {
- var width = tex.width;
- var height = tex.height;
-
- if(this.properties.size != 0)
- {
- width = this.properties.size;
- height = this.properties.size;
- }
-
- var temp = this._temp_texture;
-
- var type = tex.type;
- if(this.properties.precision === LGraphTexture.LOW)
- type = gl.UNSIGNED_BYTE;
- else if(this.properties.precision === LGraphTexture.HIGH)
- type = gl.HIGH_PRECISION_FORMAT;
-
- if(!temp || temp.width != width || temp.height != height || temp.type != type )
- {
- var minFilter = gl.LINEAR;
- if( this.properties.generate_mipmaps && isPowerOfTwo(width) && isPowerOfTwo(height) )
- minFilter = gl.LINEAR_MIPMAP_LINEAR;
- this._temp_texture = new GL.Texture( width, height, { type: type, format: gl.RGBA, minFilter: minFilter, magFilter: gl.LINEAR });
- }
- tex.copyTo(this._temp_texture);
-
- if(this.properties.generate_mipmaps)
- {
- this._temp_texture.bind(0);
- gl.generateMipmap(this._temp_texture.texture_type);
- this._temp_texture.unbind(0);
- }
- }
-
-
- this.setOutputData(0,this._temp_texture);
- }
-
- LiteGraph.registerNodeType("texture/copy", LGraphTextureCopy );
-
-
- // Texture Downsample *****************************************
- function LGraphTextureDownsample()
- {
- this.addInput("Texture","Texture");
- this.addOutput("","Texture");
- this.properties = { iterations: 1, generate_mipmaps: false, precision: LGraphTexture.DEFAULT };
- }
-
- LGraphTextureDownsample.title = "Downsample";
- LGraphTextureDownsample.desc = "Downsample Texture";
- LGraphTextureDownsample.widgets_info = {
- iterations: { type:"number", step: 1, precision: 0, min: 1 },
- precision: { widget:"combo", values: LGraphTexture.MODE_VALUES }
- };
-
- LGraphTextureDownsample.prototype.onExecute = function()
- {
- var tex = this.getInputData(0);
- if(!tex && !this._temp_texture)
- return;
-
- if(!this.isOutputConnected(0))
- return; //saves work
-
- //we do not allow any texture different than texture 2D
- if(!tex || tex.texture_type !== GL.TEXTURE_2D )
- return;
-
- var shader = LGraphTextureDownsample._shader;
- if(!shader)
- LGraphTextureDownsample._shader = shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureDownsample.pixel_shader );
-
- var width = tex.width|0;
- var height = tex.height|0;
- var type = tex.type;
- if(this.properties.precision === LGraphTexture.LOW)
- type = gl.UNSIGNED_BYTE;
- else if(this.properties.precision === LGraphTexture.HIGH)
- type = gl.HIGH_PRECISION_FORMAT;
- var iterations = this.properties.iterations || 1;
-
- var origin = tex;
- var target = null;
-
- var temp = [];
- var options = {
- type: type,
- format: tex.format
- };
-
- var offset = vec2.create();
- var uniforms = {
- u_offset: offset
- };
-
- if( this._texture )
- GL.Texture.releaseTemporary( this._texture );
-
- for(var i = 0; i < iterations; ++i)
- {
- offset[0] = 1/width;
- offset[1] = 1/height;
- width = width>>1 || 0;
- height = height>>1 || 0;
- target = GL.Texture.getTemporary( width, height, options );
- temp.push( target );
- origin.setParameter( GL.TEXTURE_MAG_FILTER, GL.NEAREST );
- origin.copyTo( target, shader, uniforms );
- if(width == 1 && height == 1)
- break; //nothing else to do
- origin = target;
- }
-
- //keep the last texture used
- this._texture = temp.pop();
-
- //free the rest
- for(var i = 0; i < temp.length; ++i)
- GL.Texture.releaseTemporary( temp[i] );
-
- if(this.properties.generate_mipmaps)
- {
- this._texture.bind(0);
- gl.generateMipmap(this._texture.texture_type);
- this._texture.unbind(0);
- }
-
- this.setOutputData(0,this._texture);
- }
-
- LGraphTextureDownsample.pixel_shader = "precision highp float;\n\
- precision highp float;\n\
- uniform sampler2D u_texture;\n\
- uniform vec2 u_offset;\n\
- varying vec2 v_coord;\n\
- \n\
- void main() {\n\
- vec4 color = texture2D(u_texture, v_coord );\n\
- color += texture2D(u_texture, v_coord + vec2( u_offset.x, 0.0 ) );\n\
- color += texture2D(u_texture, v_coord + vec2( 0.0, u_offset.y ) );\n\
- color += texture2D(u_texture, v_coord + vec2( u_offset.x, u_offset.y ) );\n\
- gl_FragColor = color * 0.25;\n\
- }\n\
- ";
-
- LiteGraph.registerNodeType("texture/downsample", LGraphTextureDownsample );
-
-
-
- // Texture Copy *****************************************
- function LGraphTextureAverage()
- {
- this.addInput("Texture","Texture");
- this.addOutput("tex","Texture");
- this.addOutput("avg","vec4");
- this.addOutput("lum","number");
- this.properties = { mipmap_offset: 0, low_precision: false };
-
- this._uniforms = { u_texture: 0, u_mipmap_offset: this.properties.mipmap_offset };
- this._luminance = new Float32Array(4);
- }
-
- LGraphTextureAverage.title = "Average";
- LGraphTextureAverage.desc = "Compute a partial average (32 random samples) of a texture and stores it as a 1x1 pixel texture";
-
- LGraphTextureAverage.prototype.onExecute = function()
- {
- var tex = this.getInputData(0);
- if(!tex)
- return;
-
- if(!this.isOutputConnected(0) && !this.isOutputConnected(1) && !this.isOutputConnected(2))
- return; //saves work
-
- if(!LGraphTextureAverage._shader)
- {
- LGraphTextureAverage._shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureAverage.pixel_shader);
- //creates 32 random numbers and stores the, in two mat4
- var samples = new Float32Array(32);
- for(var i = 0; i < 32; ++i)
- samples[i] = Math.random();
- LGraphTextureAverage._shader.uniforms({u_samples_a: samples.subarray(0,16), u_samples_b: samples.subarray(16,32) });
- }
-
- var temp = this._temp_texture;
- var type = gl.UNSIGNED_BYTE;
- if(tex.type != type) //force floats, half floats cannot be read with gl.readPixels
- type = gl.FLOAT;
-
- if(!temp || temp.type != type )
- this._temp_texture = new GL.Texture( 1, 1, { type: type, format: gl.RGBA, filter: gl.NEAREST });
-
- var shader = LGraphTextureAverage._shader;
- var uniforms = this._uniforms;
- uniforms.u_mipmap_offset = this.properties.mipmap_offset;
- this._temp_texture.drawTo(function(){
- tex.toViewport( shader, uniforms );
- });
-
- this.setOutputData(0,this._temp_texture);
-
- if(this.isOutputConnected(1) || this.isOutputConnected(2))
- {
- var pixel = this._temp_texture.getPixels();
- if(pixel)
- {
- var v = this._luminance;
- var type = this._temp_texture.type;
- v.set( pixel );
- if(type == gl.UNSIGNED_BYTE)
- vec4.scale( v,v, 1/255 );
- else if(type == GL.HALF_FLOAT || type == GL.HALF_FLOAT_OES)
- vec4.scale( v,v, 1/(255*255) ); //is this correct?
- this.setOutputData(1,v);
- this.setOutputData(2,(v[0] + v[1] + v[2]) / 3);
- }
-
- }
- }
-
- LGraphTextureAverage.pixel_shader = "precision highp float;\n\
- precision highp float;\n\
- uniform mat4 u_samples_a;\n\
- uniform mat4 u_samples_b;\n\
- uniform sampler2D u_texture;\n\
- uniform float u_mipmap_offset;\n\
- varying vec2 v_coord;\n\
- \n\
- void main() {\n\
- vec4 color = vec4(0.0);\n\
- for(int i = 0; i < 4; ++i)\n\
- for(int j = 0; j < 4; ++j)\n\
- {\n\
- color += texture2D(u_texture, vec2( u_samples_a[i][j], u_samples_b[i][j] ), u_mipmap_offset );\n\
- color += texture2D(u_texture, vec2( 1.0 - u_samples_a[i][j], 1.0 - u_samples_b[i][j] ), u_mipmap_offset );\n\
- }\n\
- gl_FragColor = color * 0.03125;\n\
- }\n\
- ";
-
- LiteGraph.registerNodeType("texture/average", LGraphTextureAverage );
-
- // Image To Texture *****************************************
- function LGraphImageToTexture()
- {
- this.addInput("Image","image");
- this.addOutput("","Texture");
- this.properties = {};
- }
-
- LGraphImageToTexture.title = "Image to Texture";
- LGraphImageToTexture.desc = "Uploads an image to the GPU";
- //LGraphImageToTexture.widgets_info = { size: { widget:"combo", values:[0,32,64,128,256,512,1024,2048]} };
-
- LGraphImageToTexture.prototype.onExecute = function()
- {
- var img = this.getInputData(0);
- if(!img)
- return;
-
- var width = img.videoWidth || img.width;
- var height = img.videoHeight || img.height;
-
- //this is in case we are using a webgl canvas already, no need to reupload it
- if(img.gltexture)
- {
- this.setOutputData(0,img.gltexture);
- return;
- }
-
-
- var temp = this._temp_texture;
- if(!temp || temp.width != width || temp.height != height )
- this._temp_texture = new GL.Texture( width, height, { format: gl.RGBA, filter: gl.LINEAR });
-
- try
- {
- this._temp_texture.uploadImage(img);
- }
- catch(err)
- {
- console.error("image comes from an unsafe location, cannot be uploaded to webgl: " + err);
- return;
- }
-
- this.setOutputData(0,this._temp_texture);
- }
-
- LiteGraph.registerNodeType("texture/imageToTexture", LGraphImageToTexture );
-
-
- // Texture LUT *****************************************
- function LGraphTextureLUT()
- {
- this.addInput("Texture","Texture");
- this.addInput("LUT","Texture");
- this.addInput("Intensity","number");
- this.addOutput("","Texture");
- this.properties = { intensity: 1, precision: LGraphTexture.DEFAULT, texture: null };
-
- if(!LGraphTextureLUT._shader)
- LGraphTextureLUT._shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphTextureLUT.pixel_shader );
- }
-
- LGraphTextureLUT.widgets_info = {
- "texture": { widget:"texture"},
- "precision": { widget:"combo", values: LGraphTexture.MODE_VALUES }
- };
-
- LGraphTextureLUT.title = "LUT";
- LGraphTextureLUT.desc = "Apply LUT to Texture";
-
- LGraphTextureLUT.prototype.onExecute = function()
- {
- if(!this.isOutputConnected(0))
- return; //saves work
-
- var tex = this.getInputData(0);
-
- if(this.properties.precision === LGraphTexture.PASS_THROUGH )
- {
- this.setOutputData(0,tex);
- return;
- }
-
- if(!tex)
- return;
-
- var lut_tex = this.getInputData(1);
-
- if(!lut_tex)
- lut_tex = LGraphTexture.getTexture( this.properties.texture );
-
- if(!lut_tex)
- {
- this.setOutputData(0,tex);
- return;
- }
-
- lut_tex.bind(0);
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR );
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE );
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE );
- gl.bindTexture(gl.TEXTURE_2D, null);
-
- var intensity = this.properties.intensity;
- if( this.isInputConnected(2) )
- this.properties.intensity = intensity = this.getInputData(2);
-
- this._tex = LGraphTexture.getTargetTexture( tex, this._tex, this.properties.precision );
-
- //var mesh = Mesh.getScreenQuad();
-
- this._tex.drawTo(function() {
- lut_tex.bind(1);
- tex.toViewport( LGraphTextureLUT._shader, {u_texture:0, u_textureB:1, u_amount: intensity} );
- });
-
- this.setOutputData(0,this._tex);
- }
-
- LGraphTextureLUT.pixel_shader = "precision highp float;\n\
- precision highp float;\n\
- varying vec2 v_coord;\n\
- uniform sampler2D u_texture;\n\
- uniform sampler2D u_textureB;\n\
- uniform float u_amount;\n\
- \n\
- void main() {\n\
- lowp vec4 textureColor = clamp( texture2D(u_texture, v_coord), vec4(0.0), vec4(1.0) );\n\
- mediump float blueColor = textureColor.b * 63.0;\n\
- mediump vec2 quad1;\n\
- quad1.y = floor(floor(blueColor) / 8.0);\n\
- quad1.x = floor(blueColor) - (quad1.y * 8.0);\n\
- mediump vec2 quad2;\n\
- quad2.y = floor(ceil(blueColor) / 8.0);\n\
- quad2.x = ceil(blueColor) - (quad2.y * 8.0);\n\
- highp vec2 texPos1;\n\
- texPos1.x = (quad1.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r);\n\
- texPos1.y = 1.0 - ((quad1.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.g));\n\
- highp vec2 texPos2;\n\
- texPos2.x = (quad2.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r);\n\
- texPos2.y = 1.0 - ((quad2.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.g));\n\
- lowp vec4 newColor1 = texture2D(u_textureB, texPos1);\n\
- lowp vec4 newColor2 = texture2D(u_textureB, texPos2);\n\
- lowp vec4 newColor = mix(newColor1, newColor2, fract(blueColor));\n\
- gl_FragColor = vec4( mix( textureColor.rgb, newColor.rgb, u_amount), textureColor.w);\n\
- }\n\
- ";
-
- LiteGraph.registerNodeType("texture/LUT", LGraphTextureLUT );
-
- // Texture Channels *****************************************
- function LGraphTextureChannels()
- {
- this.addInput("Texture","Texture");
-
- this.addOutput("R","Texture");
- this.addOutput("G","Texture");
- this.addOutput("B","Texture");
- this.addOutput("A","Texture");
-
- this.properties = {};
- if(!LGraphTextureChannels._shader)
- LGraphTextureChannels._shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphTextureChannels.pixel_shader );
- }
-
- LGraphTextureChannels.title = "Texture to Channels";
- LGraphTextureChannels.desc = "Split texture channels";
-
- LGraphTextureChannels.prototype.onExecute = function()
- {
- var texA = this.getInputData(0);
- if(!texA) return;
-
- if(!this._channels)
- this._channels = Array(4);
-
- var connections = 0;
- for(var i = 0; i < 4; i++)
- {
- if(this.isOutputConnected(i))
- {
- if(!this._channels[i] || this._channels[i].width != texA.width || this._channels[i].height != texA.height || this._channels[i].type != texA.type)
- this._channels[i] = new GL.Texture( texA.width, texA.height, { type: texA.type, format: gl.RGBA, filter: gl.LINEAR });
- connections++;
- }
- else
- this._channels[i] = null;
- }
-
- if(!connections)
- return;
-
- gl.disable( gl.BLEND );
- gl.disable( gl.DEPTH_TEST );
-
- var mesh = Mesh.getScreenQuad();
- var shader = LGraphTextureChannels._shader;
- var masks = [[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]];
-
- for(var i = 0; i < 4; i++)
- {
- if(!this._channels[i])
- continue;
-
- this._channels[i].drawTo( function() {
- texA.bind(0);
- shader.uniforms({u_texture:0, u_mask: masks[i]}).draw(mesh);
- });
- this.setOutputData(i, this._channels[i]);
- }
- }
-
- LGraphTextureChannels.pixel_shader = "precision highp float;\n\
- precision highp float;\n\
- varying vec2 v_coord;\n\
- uniform sampler2D u_texture;\n\
- uniform vec4 u_mask;\n\
- \n\
- void main() {\n\
- gl_FragColor = vec4( vec3( length( texture2D(u_texture, v_coord) * u_mask )), 1.0 );\n\
- }\n\
- ";
-
- LiteGraph.registerNodeType("texture/textureChannels", LGraphTextureChannels );
-
-
- // Texture Channels to Texture *****************************************
- function LGraphChannelsTexture()
- {
- this.addInput("R","Texture");
- this.addInput("G","Texture");
- this.addInput("B","Texture");
- this.addInput("A","Texture");
-
- this.addOutput("Texture","Texture");
-
- this.properties = {};
- if(!LGraphChannelsTexture._shader)
- LGraphChannelsTexture._shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphChannelsTexture.pixel_shader );
- }
-
- LGraphChannelsTexture.title = "Channels to Texture";
- LGraphChannelsTexture.desc = "Split texture channels";
-
- LGraphChannelsTexture.prototype.onExecute = function()
- {
- var tex = [ this.getInputData(0),
- this.getInputData(1),
- this.getInputData(2),
- this.getInputData(3) ];
-
- if(!tex[0] || !tex[1] || !tex[2] || !tex[3])
- return;
-
- gl.disable( gl.BLEND );
- gl.disable( gl.DEPTH_TEST );
-
- var mesh = Mesh.getScreenQuad();
- var shader = LGraphChannelsTexture._shader;
-
- this._tex = LGraphTexture.getTargetTexture( tex[0], this._tex );
-
- this._tex.drawTo( function() {
- tex[0].bind(0);
- tex[1].bind(1);
- tex[2].bind(2);
- tex[3].bind(3);
- shader.uniforms({u_textureR:0, u_textureG:1, u_textureB:2, u_textureA:3 }).draw(mesh);
- });
- this.setOutputData(0, this._tex);
- }
-
- LGraphChannelsTexture.pixel_shader = "precision highp float;\n\
- precision highp float;\n\
- varying vec2 v_coord;\n\
- uniform sampler2D u_textureR;\n\
- uniform sampler2D u_textureG;\n\
- uniform sampler2D u_textureB;\n\
- uniform sampler2D u_textureA;\n\
- \n\
- void main() {\n\
- gl_FragColor = vec4( \
- texture2D(u_textureR, v_coord).r,\
- texture2D(u_textureG, v_coord).r,\
- texture2D(u_textureB, v_coord).r,\
- texture2D(u_textureA, v_coord).r);\n\
- }\n\
- ";
-
- LiteGraph.registerNodeType("texture/channelsTexture", LGraphChannelsTexture );
-
- // Texture Channels to Texture *****************************************
- function LGraphTextureGradient()
- {
- this.addInput("A","color");
- this.addInput("B","color");
- this.addOutput("Texture","Texture");
-
- this.properties = { angle: 0, scale: 1, A:[0,0,0], B:[1,1,1], texture_size:32 };
- if(!LGraphTextureGradient._shader)
- LGraphTextureGradient._shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphTextureGradient.pixel_shader );
-
- this._uniforms = { u_angle: 0, u_colorA: vec3.create(), u_colorB: vec3.create()};
- }
-
- LGraphTextureGradient.title = "Gradient";
- LGraphTextureGradient.desc = "Generates a gradient";
- LGraphTextureGradient["@A"] = { type:"color" };
- LGraphTextureGradient["@B"] = { type:"color" };
- LGraphTextureGradient["@texture_size"] = { type:"enum", values:[32,64,128,256,512] };
-
- LGraphTextureGradient.prototype.onExecute = function()
- {
- gl.disable( gl.BLEND );
- gl.disable( gl.DEPTH_TEST );
-
- var mesh = GL.Mesh.getScreenQuad();
- var shader = LGraphTextureGradient._shader;
-
- var A = this.getInputData(0);
- if(!A)
- A = this.properties.A;
- var B = this.getInputData(1);
- if(!B)
- B = this.properties.B;
-
- //angle and scale
- for(var i = 2; i < this.inputs.length; i++)
- {
- var input = this.inputs[i];
- var v = this.getInputData(i);
- if(v === undefined)
- continue;
- this.properties[ input.name ] = v;
- }
-
- var uniforms = this._uniforms;
- this._uniforms.u_angle = this.properties.angle * DEG2RAD;
- this._uniforms.u_scale = this.properties.scale;
- vec3.copy( uniforms.u_colorA, A );
- vec3.copy( uniforms.u_colorB, B );
-
- var size = parseInt( this.properties.texture_size );
- if(!this._tex || this._tex.width != size )
- this._tex = new GL.Texture( size, size, { format: gl.RGB, filter: gl.LINEAR });
-
- this._tex.drawTo( function() {
- shader.uniforms(uniforms).draw(mesh);
- });
- this.setOutputData(0, this._tex);
- }
-
- LGraphTextureGradient.prototype.onGetInputs = function()
- {
- return [["angle","number"],["scale","number"]];
- }
-
- LGraphTextureGradient.pixel_shader = "precision highp float;\n\
- precision highp float;\n\
- varying vec2 v_coord;\n\
- uniform float u_angle;\n\
- uniform float u_scale;\n\
- uniform vec3 u_colorA;\n\
- uniform vec3 u_colorB;\n\
- \n\
- vec2 rotate(vec2 v, float angle)\n\
- {\n\
- vec2 result;\n\
- float _cos = cos(angle);\n\
- float _sin = sin(angle);\n\
- result.x = v.x * _cos - v.y * _sin;\n\
- result.y = v.x * _sin + v.y * _cos;\n\
- return result;\n\
- }\n\
- void main() {\n\
- float f = (rotate(u_scale * (v_coord - vec2(0.5)), u_angle) + vec2(0.5)).x;\n\
- vec3 color = mix(u_colorA,u_colorB,clamp(f,0.0,1.0));\n\
- gl_FragColor = vec4(color,1.0);\n\
- }\n\
- ";
-
- LiteGraph.registerNodeType("texture/gradient", LGraphTextureGradient );
-
- // Texture Mix *****************************************
- function LGraphTextureMix()
- {
- this.addInput("A","Texture");
- this.addInput("B","Texture");
- this.addInput("Mixer","Texture");
-
- this.addOutput("Texture","Texture");
- this.properties = { precision: LGraphTexture.DEFAULT };
-
- if(!LGraphTextureMix._shader)
- LGraphTextureMix._shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphTextureMix.pixel_shader );
- }
-
- LGraphTextureMix.title = "Mix";
- LGraphTextureMix.desc = "Generates a texture mixing two textures";
-
- LGraphTextureMix.widgets_info = {
- "precision": { widget:"combo", values: LGraphTexture.MODE_VALUES }
- };
-
- LGraphTextureMix.prototype.onExecute = function()
- {
- var texA = this.getInputData(0);
-
- if(!this.isOutputConnected(0))
- return; //saves work
-
- if(this.properties.precision === LGraphTexture.PASS_THROUGH )
- {
- this.setOutputData(0,texA);
- return;
- }
-
- var texB = this.getInputData(1);
- var texMix = this.getInputData(2);
- if(!texA || !texB || !texMix) return;
-
- this._tex = LGraphTexture.getTargetTexture( texA, this._tex, this.properties.precision );
-
- gl.disable( gl.BLEND );
- gl.disable( gl.DEPTH_TEST );
-
- var mesh = Mesh.getScreenQuad();
- var shader = LGraphTextureMix._shader;
-
- this._tex.drawTo( function() {
- texA.bind(0);
- texB.bind(1);
- texMix.bind(2);
- shader.uniforms({u_textureA:0,u_textureB:1,u_textureMix:2}).draw(mesh);
- });
-
- this.setOutputData(0, this._tex);
- }
-
- LGraphTextureMix.pixel_shader = "precision highp float;\n\
- precision highp float;\n\
- varying vec2 v_coord;\n\
- uniform sampler2D u_textureA;\n\
- uniform sampler2D u_textureB;\n\
- uniform sampler2D u_textureMix;\n\
- \n\
- void main() {\n\
- gl_FragColor = mix( texture2D(u_textureA, v_coord), texture2D(u_textureB, v_coord), texture2D(u_textureMix, v_coord) );\n\
- }\n\
- ";
-
- LiteGraph.registerNodeType("texture/mix", LGraphTextureMix );
-
- // Texture Edges detection *****************************************
- function LGraphTextureEdges()
- {
- this.addInput("Tex.","Texture");
-
- this.addOutput("Edges","Texture");
- this.properties = { invert: true, threshold: false, factor: 1, precision: LGraphTexture.DEFAULT };
-
- if(!LGraphTextureEdges._shader)
- LGraphTextureEdges._shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphTextureEdges.pixel_shader );
- }
-
- LGraphTextureEdges.title = "Edges";
- LGraphTextureEdges.desc = "Detects edges";
-
- LGraphTextureEdges.widgets_info = {
- "precision": { widget:"combo", values: LGraphTexture.MODE_VALUES }
- };
-
- LGraphTextureEdges.prototype.onExecute = function()
- {
- if(!this.isOutputConnected(0))
- return; //saves work
-
- var tex = this.getInputData(0);
-
- if(this.properties.precision === LGraphTexture.PASS_THROUGH )
- {
- this.setOutputData(0,tex);
- return;
- }
-
- if(!tex) return;
-
- this._tex = LGraphTexture.getTargetTexture( tex, this._tex, this.properties.precision );
-
- gl.disable( gl.BLEND );
- gl.disable( gl.DEPTH_TEST );
-
- var mesh = Mesh.getScreenQuad();
- var shader = LGraphTextureEdges._shader;
- var invert = this.properties.invert;
- var factor = this.properties.factor;
- var threshold = this.properties.threshold ? 1 : 0;
-
- this._tex.drawTo( function() {
- tex.bind(0);
- shader.uniforms({u_texture:0, u_isize:[1/tex.width,1/tex.height], u_factor: factor, u_threshold: threshold, u_invert: invert ? 1 : 0}).draw(mesh);
- });
-
- this.setOutputData(0, this._tex);
- }
-
- LGraphTextureEdges.pixel_shader = "precision highp float;\n\
- precision highp float;\n\
- varying vec2 v_coord;\n\
- uniform sampler2D u_texture;\n\
- uniform vec2 u_isize;\n\
- uniform int u_invert;\n\
- uniform float u_factor;\n\
- uniform float u_threshold;\n\
- \n\
- void main() {\n\
- vec4 center = texture2D(u_texture, v_coord);\n\
- vec4 up = texture2D(u_texture, v_coord + u_isize * vec2(0.0,1.0) );\n\
- vec4 down = texture2D(u_texture, v_coord + u_isize * vec2(0.0,-1.0) );\n\
- vec4 left = texture2D(u_texture, v_coord + u_isize * vec2(1.0,0.0) );\n\
- vec4 right = texture2D(u_texture, v_coord + u_isize * vec2(-1.0,0.0) );\n\
- vec4 diff = abs(center - up) + abs(center - down) + abs(center - left) + abs(center - right);\n\
- diff *= u_factor;\n\
- if(u_invert == 1)\n\
- diff.xyz = vec3(1.0) - diff.xyz;\n\
- if( u_threshold == 0.0 )\n\
- gl_FragColor = vec4( diff.xyz, center.a );\n\
- else\n\
- gl_FragColor = vec4( diff.x > 0.5 ? 1.0 : 0.0, diff.y > 0.5 ? 1.0 : 0.0, diff.z > 0.5 ? 1.0 : 0.0, center.a );\n\
- }\n\
- ";
-
- LiteGraph.registerNodeType("texture/edges", LGraphTextureEdges );
-
- // Texture Depth *****************************************
- function LGraphTextureDepthRange()
- {
- this.addInput("Texture","Texture");
- this.addInput("Distance","number");
- this.addInput("Range","number");
- this.addOutput("Texture","Texture");
- this.properties = { distance:100, range: 50, only_depth: false, high_precision: false };
- this._uniforms = {u_texture:0, u_distance: 100, u_range: 50, u_camera_planes: null };
- }
-
- LGraphTextureDepthRange.title = "Depth Range";
- LGraphTextureDepthRange.desc = "Generates a texture with a depth range";
-
- LGraphTextureDepthRange.prototype.onExecute = function()
- {
- if(!this.isOutputConnected(0))
- return; //saves work
-
- var tex = this.getInputData(0);
- if(!tex) return;
-
- var precision = gl.UNSIGNED_BYTE;
- if(this.properties.high_precision)
- precision = gl.half_float_ext ? gl.HALF_FLOAT_OES : gl.FLOAT;
-
- if(!this._temp_texture || this._temp_texture.type != precision ||
- this._temp_texture.width != tex.width || this._temp_texture.height != tex.height)
- this._temp_texture = new GL.Texture( tex.width, tex.height, { type: precision, format: gl.RGBA, filter: gl.LINEAR });
-
- var uniforms = this._uniforms;
-
- //iterations
- var distance = this.properties.distance;
- if( this.isInputConnected(1) )
- {
- distance = this.getInputData(1);
- this.properties.distance = distance;
- }
-
- var range = this.properties.range;
- if( this.isInputConnected(2) )
- {
- range = this.getInputData(2);
- this.properties.range = range;
- }
-
- uniforms.u_distance = distance;
- uniforms.u_range = range;
-
- gl.disable( gl.BLEND );
- gl.disable( gl.DEPTH_TEST );
- var mesh = Mesh.getScreenQuad();
- if(!LGraphTextureDepthRange._shader)
- {
- LGraphTextureDepthRange._shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphTextureDepthRange.pixel_shader );
- LGraphTextureDepthRange._shader_onlydepth = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphTextureDepthRange.pixel_shader, { ONLY_DEPTH:""} );
- }
- var shader = this.properties.only_depth ? LGraphTextureDepthRange._shader_onlydepth : LGraphTextureDepthRange._shader;
-
- //NEAR AND FAR PLANES
- var planes = null;
- if( tex.near_far_planes )
- planes = tex.near_far_planes;
- else if( window.LS && LS.Renderer._main_camera )
- planes = LS.Renderer._main_camera._uniforms.u_camera_planes;
- else
- planes = [0.1,1000]; //hardcoded
- uniforms.u_camera_planes = planes;
-
-
- this._temp_texture.drawTo( function() {
- tex.bind(0);
- shader.uniforms( uniforms ).draw(mesh);
- });
-
- this._temp_texture.near_far_planes = planes;
- this.setOutputData(0, this._temp_texture );
- }
-
- LGraphTextureDepthRange.pixel_shader = "precision highp float;\n\
- precision highp float;\n\
- varying vec2 v_coord;\n\
- uniform sampler2D u_texture;\n\
- uniform vec2 u_camera_planes;\n\
- uniform float u_distance;\n\
- uniform float u_range;\n\
- \n\
- float LinearDepth()\n\
- {\n\
- float zNear = u_camera_planes.x;\n\
- float zFar = u_camera_planes.y;\n\
- float depth = texture2D(u_texture, v_coord).x;\n\
- depth = depth * 2.0 - 1.0;\n\
- return zNear * (depth + 1.0) / (zFar + zNear - depth * (zFar - zNear));\n\
- }\n\
- \n\
- void main() {\n\
- float depth = LinearDepth();\n\
- #ifdef ONLY_DEPTH\n\
- gl_FragColor = vec4(depth);\n\
- #else\n\
- float diff = abs(depth * u_camera_planes.y - u_distance);\n\
- float dof = 1.0;\n\
- if(diff <= u_range)\n\
- dof = diff / u_range;\n\
- gl_FragColor = vec4(dof);\n\
- #endif\n\
- }\n\
- ";
-
- LiteGraph.registerNodeType("texture/depth_range", LGraphTextureDepthRange );
-
- // Texture Blur *****************************************
- function LGraphTextureBlur()
- {
- this.addInput("Texture","Texture");
- this.addInput("Iterations","number");
- this.addInput("Intensity","number");
- this.addOutput("Blurred","Texture");
- this.properties = { intensity: 1, iterations: 1, preserve_aspect: false, scale:[1,1], precision: LGraphTexture.DEFAULT };
- }
-
- LGraphTextureBlur.title = "Blur";
- LGraphTextureBlur.desc = "Blur a texture";
-
- LGraphTextureBlur.widgets_info = {
- precision: { widget:"combo", values: LGraphTexture.MODE_VALUES }
- };
-
- LGraphTextureBlur.max_iterations = 20;
-
- LGraphTextureBlur.prototype.onExecute = function()
- {
- var tex = this.getInputData(0);
- if(!tex)
- return;
-
- if(!this.isOutputConnected(0))
- return; //saves work
-
- var temp = this._final_texture;
-
- if(!temp || temp.width != tex.width || temp.height != tex.height || temp.type != tex.type )
- {
- //we need two textures to do the blurring
- //this._temp_texture = new GL.Texture( tex.width, tex.height, { type: tex.type, format: gl.RGBA, filter: gl.LINEAR });
- temp = this._final_texture = new GL.Texture( tex.width, tex.height, { type: tex.type, format: gl.RGBA, filter: gl.LINEAR });
- }
-
- //iterations
- var iterations = this.properties.iterations;
- if( this.isInputConnected(1) )
- {
- iterations = this.getInputData(1);
- this.properties.iterations = iterations;
- }
- iterations = Math.min( Math.floor(iterations), LGraphTextureBlur.max_iterations );
- if(iterations == 0) //skip blurring
- {
- this.setOutputData(0, tex);
- return;
- }
-
- var intensity = this.properties.intensity;
- if( this.isInputConnected(2) )
- {
- intensity = this.getInputData(2);
- this.properties.intensity = intensity;
- }
-
- //blur sometimes needs an aspect correction
- var aspect = LiteGraph.camera_aspect;
- if(!aspect && window.gl !== undefined)
- aspect = gl.canvas.height / gl.canvas.width;
- if(!aspect)
- aspect = 1;
- aspect = this.properties.preserve_aspect ? aspect : 1;
-
- var scale = this.properties.scale || [1,1];
- tex.applyBlur( aspect * scale[0], scale[1], intensity, temp );
- for(var i = 1; i < iterations; ++i)
- temp.applyBlur( aspect * scale[0] * (i+1), scale[1] * (i+1), intensity );
-
- this.setOutputData(0, temp );
- }
-
- /*
- LGraphTextureBlur.pixel_shader = "precision highp float;\n\
- precision highp float;\n\
- varying vec2 v_coord;\n\
- uniform sampler2D u_texture;\n\
- uniform vec2 u_offset;\n\
- uniform float u_intensity;\n\
- void main() {\n\
- vec4 sum = vec4(0.0);\n\
- vec4 center = texture2D(u_texture, v_coord);\n\
- sum += texture2D(u_texture, v_coord + u_offset * -4.0) * 0.05/0.98;\n\
- sum += texture2D(u_texture, v_coord + u_offset * -3.0) * 0.09/0.98;\n\
- sum += texture2D(u_texture, v_coord + u_offset * -2.0) * 0.12/0.98;\n\
- sum += texture2D(u_texture, v_coord + u_offset * -1.0) * 0.15/0.98;\n\
- sum += center * 0.16/0.98;\n\
- sum += texture2D(u_texture, v_coord + u_offset * 4.0) * 0.05/0.98;\n\
- sum += texture2D(u_texture, v_coord + u_offset * 3.0) * 0.09/0.98;\n\
- sum += texture2D(u_texture, v_coord + u_offset * 2.0) * 0.12/0.98;\n\
- sum += texture2D(u_texture, v_coord + u_offset * 1.0) * 0.15/0.98;\n\
- gl_FragColor = u_intensity * sum;\n\
- }\n\
- ";
- */
-
- LiteGraph.registerNodeType("texture/blur", LGraphTextureBlur );
-
-
- // Texture Glow *****************************************
- //based in https://catlikecoding.com/unity/tutorials/advanced-rendering/bloom/
- function LGraphTextureGlow()
- {
- this.addInput("in","Texture");
- this.addInput("dirt","Texture");
- this.addOutput("out","Texture");
- this.addOutput("glow","Texture");
- this.properties = { enabled: true, intensity: 1, persistence: 0.99, iterations:16, threshold:0, scale: 1, dirt_factor: 0.5, precision: LGraphTexture.DEFAULT };
- this._textures = [];
- this._uniforms = { u_intensity: 1, u_texture: 0, u_glow_texture: 1, u_threshold: 0, u_texel_size: vec2.create() };
- }
-
- LGraphTextureGlow.title = "Glow";
- LGraphTextureGlow.desc = "Filters a texture giving it a glow effect";
- LGraphTextureGlow.weights = new Float32Array( [0.5,0.4,0.3,0.2] );
-
- LGraphTextureGlow.widgets_info = {
- "iterations": { type:"number", min: 0, max: 16, step: 1, precision: 0 },
- "threshold": { type:"number", min: 0, max: 10, step: 0.01, precision: 2 },
- "precision": { widget:"combo", values: LGraphTexture.MODE_VALUES }
- };
-
- LGraphTextureGlow.prototype.onGetInputs = function(){
- return [["enabled","boolean"],["threshold","number"],["intensity","number"],["persistence","number"],["iterations","number"],["dirt_factor","number"]];
- }
-
- LGraphTextureGlow.prototype.onGetOutputs = function(){
- return [["average","Texture"]];
- }
-
- LGraphTextureGlow.prototype.onExecute = function()
- {
- var tex = this.getInputData(0);
- if(!tex)
- return;
-
- if(!this.isAnyOutputConnected())
- return; //saves work
-
- if(this.properties.precision === LGraphTexture.PASS_THROUGH || this.getInputOrProperty("enabled" ) === false )
- {
- this.setOutputData(0,tex);
- return;
- }
-
- var width = tex.width;
- var height = tex.height;
-
- var texture_info = { format: tex.format, type: tex.type, minFilter: GL.LINEAR, magFilter: GL.LINEAR, wrap: gl.CLAMP_TO_EDGE };
- var type = LGraphTexture.getTextureType( this.properties.precision, tex );
-
- var uniforms = this._uniforms;
- var textures = this._textures;
-
- //cut
- var shader = LGraphTextureGlow._cut_shader;
- if(!shader)
- shader = LGraphTextureGlow._cut_shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureGlow.cut_pixel_shader );
-
- gl.disable( gl.DEPTH_TEST );
- gl.disable( gl.BLEND );
-
- uniforms.u_threshold = this.getInputOrProperty("threshold");
- var currentDestination = textures[0] = GL.Texture.getTemporary( width, height, texture_info );
- tex.blit( currentDestination, shader.uniforms(uniforms) );
- var currentSource = currentDestination;
-
- var iterations = this.getInputOrProperty("iterations");
- iterations = Math.clamp( iterations, 1, 16) | 0;
- var texel_size = uniforms.u_texel_size;
- var intensity = this.getInputOrProperty("intensity");
-
- uniforms.u_intensity = 1;
- uniforms.u_delta = this.properties.scale; //1
-
- //downscale/upscale shader
- var shader = LGraphTextureGlow._shader;
- if(!shader)
- shader = LGraphTextureGlow._shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureGlow.scale_pixel_shader );
-
- var i = 1;
- //downscale
- for (;i < iterations; i++) {
- width = width>>1;
- if( (height|0) > 1 )
- height = height>>1;
- if( width < 2 )
- break;
- currentDestination = textures[i] = GL.Texture.getTemporary( width, height, texture_info );
- texel_size[0] = 1 / currentSource.width; texel_size[1] = 1 / currentSource.height;
- currentSource.blit( currentDestination, shader.uniforms(uniforms) );
- currentSource = currentDestination;
- }
-
- //average
- if(this.isOutputConnected(2))
- {
- var average_texture = this._average_texture;
- if(!average_texture || average_texture.type != tex.type || average_texture.format != tex.format )
- average_texture = this._average_texture = new GL.Texture( 1, 1, { type: tex.type, format: tex.format, filter: gl.LINEAR });
- texel_size[0] = 1 / currentSource.width; texel_size[1] = 1 / currentSource.height;
- uniforms.u_intensity = intensity;
- uniforms.u_delta = 1;
- currentSource.blit( average_texture, shader.uniforms(uniforms) );
- this.setOutputData( 2, average_texture );
- }
-
- //upscale and blend
- gl.enable( gl.BLEND );
- gl.blendFunc( gl.ONE, gl.ONE );
- uniforms.u_intensity = this.getInputOrProperty("persistence");
- uniforms.u_delta = 0.5;
-
- for (i -= 2; i >= 0; i--) // i-=2 => -1 to point to last element in array, -1 to go to texture above
- {
- currentDestination = textures[i];
- textures[i] = null;
- texel_size[0] = 1 / currentSource.width; texel_size[1] = 1 / currentSource.height;
- currentSource.blit( currentDestination, shader.uniforms(uniforms) );
- GL.Texture.releaseTemporary( currentSource );
- currentSource = currentDestination;
- }
- gl.disable( gl.BLEND );
-
- //glow
- if(this.isOutputConnected(1))
- {
- var glow_texture = this._glow_texture;
- if(!glow_texture || glow_texture.width != tex.width || glow_texture.height != tex.height || glow_texture.type != type || glow_texture.format != tex.format )
- glow_texture = this._glow_texture = new GL.Texture( tex.width, tex.height, { type: type, format: tex.format, filter: gl.LINEAR });
- currentSource.blit( glow_texture );
- this.setOutputData( 1, glow_texture);
- }
-
- //final composition
- if(this.isOutputConnected(0))
- {
- var final_texture = this._final_texture;
- if(!final_texture || final_texture.width != tex.width || final_texture.height != tex.height || final_texture.type != type || final_texture.format != tex.format )
- final_texture = this._final_texture = new GL.Texture( tex.width, tex.height, { type: type, format: tex.format, filter: gl.LINEAR });
-
- var dirt_texture = this.getInputData(1);
- var dirt_factor = this.getInputOrProperty("dirt_factor");
-
- uniforms.u_intensity = intensity;
-
- shader = dirt_texture ? LGraphTextureGlow._dirt_final_shader : LGraphTextureGlow._final_shader;
- if(!shader)
- {
- if(dirt_texture)
- shader = LGraphTextureGlow._dirt_final_shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureGlow.final_pixel_shader, { USE_DIRT: "" } );
- else
- shader = LGraphTextureGlow._final_shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureGlow.final_pixel_shader );
- }
-
- final_texture.drawTo( function(){
- tex.bind(0);
- currentSource.bind(1);
- if(dirt_texture)
- {
- shader.setUniform( "u_dirt_factor", dirt_factor );
- shader.setUniform( "u_dirt_texture", dirt_texture.bind(2) );
- }
- shader.toViewport( uniforms );
- });
- this.setOutputData( 0, final_texture );
- }
-
- GL.Texture.releaseTemporary( currentSource );
- }
-
- LGraphTextureGlow.cut_pixel_shader = "precision highp float;\n\
- varying vec2 v_coord;\n\
- uniform sampler2D u_texture;\n\
- uniform float u_threshold;\n\
- void main() {\n\
- gl_FragColor = max( texture2D( u_texture, v_coord ) - vec4( u_threshold ), vec4(0.0) );\n\
- }"
-
- LGraphTextureGlow.scale_pixel_shader = "precision highp float;\n\
- varying vec2 v_coord;\n\
- uniform sampler2D u_texture;\n\
- uniform vec2 u_texel_size;\n\
- uniform float u_delta;\n\
- uniform float u_intensity;\n\
- \n\
- vec4 sampleBox(vec2 uv) {\n\
- vec4 o = u_texel_size.xyxy * vec2(-u_delta, u_delta).xxyy;\n\
- vec4 s = texture2D( u_texture, uv + o.xy ) + texture2D( u_texture, uv + o.zy) + texture2D( u_texture, uv + o.xw) + texture2D( u_texture, uv + o.zw);\n\
- return s * 0.25;\n\
- }\n\
- void main() {\n\
- gl_FragColor = u_intensity * sampleBox( v_coord );\n\
- }"
-
- LGraphTextureGlow.final_pixel_shader = "precision highp float;\n\
- varying vec2 v_coord;\n\
- uniform sampler2D u_texture;\n\
- uniform sampler2D u_glow_texture;\n\
- #ifdef USE_DIRT\n\
- uniform sampler2D u_dirt_texture;\n\
- #endif\n\
- uniform vec2 u_texel_size;\n\
- uniform float u_delta;\n\
- uniform float u_intensity;\n\
- uniform float u_dirt_factor;\n\
- \n\
- vec4 sampleBox(vec2 uv) {\n\
- vec4 o = u_texel_size.xyxy * vec2(-u_delta, u_delta).xxyy;\n\
- vec4 s = texture2D( u_glow_texture, uv + o.xy ) + texture2D( u_glow_texture, uv + o.zy) + texture2D( u_glow_texture, uv + o.xw) + texture2D( u_glow_texture, uv + o.zw);\n\
- return s * 0.25;\n\
- }\n\
- void main() {\n\
- vec4 glow = sampleBox( v_coord );\n\
- #ifdef USE_DIRT\n\
- glow = mix( glow, glow * texture2D( u_dirt_texture, v_coord ), u_dirt_factor );\n\
- #endif\n\
- gl_FragColor = texture2D( u_texture, v_coord ) + u_intensity * glow;\n\
- }"
-
- LiteGraph.registerNodeType("texture/glow", LGraphTextureGlow );
-
-
- // Texture Blur *****************************************
- function LGraphTextureKuwaharaFilter()
- {
- this.addInput("Texture","Texture");
- this.addOutput("Filtered","Texture");
- this.properties = { intensity: 1, radius: 5 };
- }
-
- LGraphTextureKuwaharaFilter.title = "Kuwahara Filter";
- LGraphTextureKuwaharaFilter.desc = "Filters a texture giving an artistic oil canvas painting";
-
- LGraphTextureKuwaharaFilter.max_radius = 10;
- LGraphTextureKuwaharaFilter._shaders = [];
-
- LGraphTextureKuwaharaFilter.prototype.onExecute = function()
- {
- var tex = this.getInputData(0);
- if(!tex)
- return;
-
- if(!this.isOutputConnected(0))
- return; //saves work
-
- var temp = this._temp_texture;
-
- if(!temp || temp.width != tex.width || temp.height != tex.height || temp.type != tex.type )
- {
- //we need two textures to do the blurring
- this._temp_texture = new GL.Texture( tex.width, tex.height, { type: tex.type, format: gl.RGBA, filter: gl.LINEAR });
- //this._final_texture = new GL.Texture( tex.width, tex.height, { type: tex.type, format: gl.RGBA, filter: gl.LINEAR });
- }
-
- //iterations
- var radius = this.properties.radius;
- radius = Math.min( Math.floor(radius), LGraphTextureKuwaharaFilter.max_radius );
- if(radius == 0) //skip blurring
- {
- this.setOutputData(0, tex);
- return;
- }
-
- var intensity = this.properties.intensity;
-
- //blur sometimes needs an aspect correction
- var aspect = LiteGraph.camera_aspect;
- if(!aspect && window.gl !== undefined)
- aspect = gl.canvas.height / gl.canvas.width;
- if(!aspect)
- aspect = 1;
- aspect = this.properties.preserve_aspect ? aspect : 1;
-
- if(!LGraphTextureKuwaharaFilter._shaders[ radius ])
- LGraphTextureKuwaharaFilter._shaders[ radius ] = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphTextureKuwaharaFilter.pixel_shader, { RADIUS: radius.toFixed(0) });
-
- var shader = LGraphTextureKuwaharaFilter._shaders[ radius ];
- var mesh = GL.Mesh.getScreenQuad();
- tex.bind(0);
-
- this._temp_texture.drawTo( function() {
- shader.uniforms({ u_texture: 0, u_intensity: intensity, u_resolution: [tex.width, tex.height], u_iResolution: [1/tex.width,1/tex.height]}).draw(mesh);
- });
-
- this.setOutputData(0, this._temp_texture);
- }
-
-//from https://www.shadertoy.com/view/MsXSz4
-LGraphTextureKuwaharaFilter.pixel_shader = "\n\
- precision highp float;\n\
- varying vec2 v_coord;\n\
- uniform sampler2D u_texture;\n\
- uniform float u_intensity;\n\
- uniform vec2 u_resolution;\n\
- uniform vec2 u_iResolution;\n\
- #ifndef RADIUS\n\
- #define RADIUS 7\n\
- #endif\n\
- void main() {\n\
- \n\
- const int radius = RADIUS;\n\
- vec2 fragCoord = v_coord;\n\
- vec2 src_size = u_iResolution;\n\
- vec2 uv = v_coord;\n\
- float n = float((radius + 1) * (radius + 1));\n\
- int i;\n\
- int j;\n\
- vec3 m0 = vec3(0.0); vec3 m1 = vec3(0.0); vec3 m2 = vec3(0.0); vec3 m3 = vec3(0.0);\n\
- vec3 s0 = vec3(0.0); vec3 s1 = vec3(0.0); vec3 s2 = vec3(0.0); vec3 s3 = vec3(0.0);\n\
- vec3 c;\n\
- \n\
- for (int j = -radius; j <= 0; ++j) {\n\
- for (int i = -radius; i <= 0; ++i) {\n\
- c = texture2D(u_texture, uv + vec2(i,j) * src_size).rgb;\n\
- m0 += c;\n\
- s0 += c * c;\n\
- }\n\
- }\n\
- \n\
- for (int j = -radius; j <= 0; ++j) {\n\
- for (int i = 0; i <= radius; ++i) {\n\
- c = texture2D(u_texture, uv + vec2(i,j) * src_size).rgb;\n\
- m1 += c;\n\
- s1 += c * c;\n\
- }\n\
- }\n\
- \n\
- for (int j = 0; j <= radius; ++j) {\n\
- for (int i = 0; i <= radius; ++i) {\n\
- c = texture2D(u_texture, uv + vec2(i,j) * src_size).rgb;\n\
- m2 += c;\n\
- s2 += c * c;\n\
- }\n\
- }\n\
- \n\
- for (int j = 0; j <= radius; ++j) {\n\
- for (int i = -radius; i <= 0; ++i) {\n\
- c = texture2D(u_texture, uv + vec2(i,j) * src_size).rgb;\n\
- m3 += c;\n\
- s3 += c * c;\n\
- }\n\
- }\n\
- \n\
- float min_sigma2 = 1e+2;\n\
- m0 /= n;\n\
- s0 = abs(s0 / n - m0 * m0);\n\
- \n\
- float sigma2 = s0.r + s0.g + s0.b;\n\
- if (sigma2 < min_sigma2) {\n\
- min_sigma2 = sigma2;\n\
- gl_FragColor = vec4(m0, 1.0);\n\
- }\n\
- \n\
- m1 /= n;\n\
- s1 = abs(s1 / n - m1 * m1);\n\
- \n\
- sigma2 = s1.r + s1.g + s1.b;\n\
- if (sigma2 < min_sigma2) {\n\
- min_sigma2 = sigma2;\n\
- gl_FragColor = vec4(m1, 1.0);\n\
- }\n\
- \n\
- m2 /= n;\n\
- s2 = abs(s2 / n - m2 * m2);\n\
- \n\
- sigma2 = s2.r + s2.g + s2.b;\n\
- if (sigma2 < min_sigma2) {\n\
- min_sigma2 = sigma2;\n\
- gl_FragColor = vec4(m2, 1.0);\n\
- }\n\
- \n\
- m3 /= n;\n\
- s3 = abs(s3 / n - m3 * m3);\n\
- \n\
- sigma2 = s3.r + s3.g + s3.b;\n\
- if (sigma2 < min_sigma2) {\n\
- min_sigma2 = sigma2;\n\
- gl_FragColor = vec4(m3, 1.0);\n\
- }\n\
- }\n\
- ";
-
- LiteGraph.registerNodeType("texture/kuwahara", LGraphTextureKuwaharaFilter );
-
-
- // Texture Webcam *****************************************
- function LGraphTextureWebcam()
- {
- this.addOutput("Webcam","Texture");
- this.properties = { texture_name: "" };
- }
-
- LGraphTextureWebcam.title = "Webcam";
- LGraphTextureWebcam.desc = "Webcam texture";
-
-
- LGraphTextureWebcam.prototype.openStream = function()
- {
- //Vendor prefixes hell
- navigator.getUserMedia = (navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia);
- window.URL = window.URL || window.webkitURL;
-
- if (!navigator.getUserMedia) {
- //console.log('getUserMedia() is not supported in your browser, use chrome and enable WebRTC from about://flags');
- return;
- }
-
- this._waiting_confirmation = true;
- var that = this;
-
- // Not showing vendor prefixes.
- navigator.getUserMedia({video: true}, this.streamReady.bind(this), onFailSoHard);
-
- function onFailSoHard(e) {
- console.log('Webcam rejected', e);
- that._webcam_stream = false;
- that.box_color = "red";
- };
- }
-
- LGraphTextureWebcam.prototype.streamReady = function(localMediaStream)
- {
- this._webcam_stream = localMediaStream;
- //this._waiting_confirmation = false;
-
- var video = this._video;
- if(!video)
- {
- video = document.createElement("video");
- video.autoplay = true;
- video.src = window.URL.createObjectURL( localMediaStream );
- this._video = video;
- //document.body.appendChild( video ); //debug
- //when video info is loaded (size and so)
- video.onloadedmetadata = function(e) {
- // Ready to go. Do some stuff.
- console.log(e);
- };
- }
- }
-
- LGraphTextureWebcam.prototype.onRemoved = function()
- {
- if(!this._webcam_stream)
- return;
-
- var video_streams = this._webcam_stream.getVideoTracks();
- if(video_streams.length)
- {
- var webcam = video_streams[0];
- if( webcam.stop )
- webcam.stop();
- }
-
- this._webcam_stream = null;
- this._video = null;
- }
-
- LGraphTextureWebcam.prototype.onDrawBackground = function(ctx)
- {
- if(this.flags.collapsed || this.size[1] <= 20)
- return;
-
- if(!this._video)
- return;
-
- //render to graph canvas
- ctx.save();
- if(!ctx.webgl) //reverse image
- {
- ctx.translate(0,this.size[1]);
- ctx.scale(1,-1);
- ctx.drawImage(this._video, 0, 0, this.size[0], this.size[1]);
- }
- else
- {
- if(this._temp_texture)
- ctx.drawImage(this._temp_texture, 0, 0, this.size[0], this.size[1]);
- }
- ctx.restore();
- }
-
- LGraphTextureWebcam.prototype.onExecute = function()
- {
- if(this._webcam_stream == null && !this._waiting_confirmation)
- this.openStream();
-
- if(!this._video || !this._video.videoWidth)
- return;
-
- var width = this._video.videoWidth;
- var height = this._video.videoHeight;
-
- var temp = this._temp_texture;
- if(!temp || temp.width != width || temp.height != height )
- this._temp_texture = new GL.Texture( width, height, { format: gl.RGB, filter: gl.LINEAR });
-
- this._temp_texture.uploadImage( this._video );
-
- if(this.properties.texture_name)
- {
- var container = LGraphTexture.getTexturesContainer();
- container[ this.properties.texture_name ] = this._temp_texture;
- }
-
- this.setOutputData(0,this._temp_texture);
- }
-
- LiteGraph.registerNodeType("texture/webcam", LGraphTextureWebcam );
-
-
-
- //from https://github.com/spite/Wagner
- function LGraphLensFX()
- {
- this.addInput("in","Texture");
- this.addInput("f","number");
- this.addOutput("out","Texture");
- this.properties = { enabled: true, factor: 1, precision: LGraphTexture.LOW };
-
- this._uniforms = { u_texture: 0, u_factor: 1 };
- }
-
- LGraphLensFX.title = "Lens FX";
- LGraphLensFX.desc = "distortion and chromatic aberration";
-
- LGraphLensFX.widgets_info = {
- "precision": { widget:"combo", values: LGraphTexture.MODE_VALUES }
- };
-
- LGraphLensFX.prototype.onGetInputs = function() { return [["enabled","boolean"]]; }
-
- LGraphLensFX.prototype.onExecute = function()
- {
- var tex = this.getInputData(0);
- if(!tex)
- return;
-
- if(!this.isOutputConnected(0))
- return; //saves work
-
- if(this.properties.precision === LGraphTexture.PASS_THROUGH || this.getInputOrProperty("enabled" ) === false )
- {
- this.setOutputData(0, tex );
- return;
- }
-
- var temp = this._temp_texture;
- if(!temp || temp.width != tex.width || temp.height != tex.height || temp.type != tex.type )
- temp = this._temp_texture = new GL.Texture( tex.width, tex.height, { type: tex.type, format: gl.RGBA, filter: gl.LINEAR });
-
- var shader = LGraphLensFX._shader;
- if(!shader)
- shader = LGraphLensFX._shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphLensFX.pixel_shader );
-
- var factor = this.getInputData(1);
- if(factor == null)
- factor = this.properties.factor;
-
- var uniforms = this._uniforms;
- uniforms.u_factor = factor;
-
- //apply shader
- gl.disable( gl.DEPTH_TEST );
- temp.drawTo(function(){
- tex.bind(0);
- shader.uniforms(uniforms).draw( GL.Mesh.getScreenQuad() );
- });
-
- this.setOutputData(0,temp);
- }
-
- LGraphLensFX.pixel_shader = "precision highp float;\n\
- varying vec2 v_coord;\n\
- uniform sampler2D u_texture;\n\
- uniform float u_factor;\n\
- vec2 barrelDistortion(vec2 coord, float amt) {\n\
- vec2 cc = coord - 0.5;\n\
- float dist = dot(cc, cc);\n\
- return coord + cc * dist * amt;\n\
- }\n\
- \n\
- float sat( float t )\n\
- {\n\
- return clamp( t, 0.0, 1.0 );\n\
- }\n\
- \n\
- float linterp( float t ) {\n\
- return sat( 1.0 - abs( 2.0*t - 1.0 ) );\n\
- }\n\
- \n\
- float remap( float t, float a, float b ) {\n\
- return sat( (t - a) / (b - a) );\n\
- }\n\
- \n\
- vec4 spectrum_offset( float t ) {\n\
- vec4 ret;\n\
- float lo = step(t,0.5);\n\
- float hi = 1.0-lo;\n\
- float w = linterp( remap( t, 1.0/6.0, 5.0/6.0 ) );\n\
- ret = vec4(lo,1.0,hi, 1.) * vec4(1.0-w, w, 1.0-w, 1.);\n\
- \n\
- return pow( ret, vec4(1.0/2.2) );\n\
- }\n\
- \n\
- const float max_distort = 2.2;\n\
- const int num_iter = 12;\n\
- const float reci_num_iter_f = 1.0 / float(num_iter);\n\
- \n\
- void main()\n\
- { \n\
- vec2 uv=v_coord;\n\
- vec4 sumcol = vec4(0.0);\n\
- vec4 sumw = vec4(0.0); \n\
- for ( int i=0; i size || tex.height > size)
+ {
+ temp_tex = this._preview_temp_tex;
+ if(!this._preview_temp_tex)
+ {
+ temp_tex = new GL.Texture(size,size, { minFilter: gl.NEAREST });
+ this._preview_temp_tex = temp_tex;
+ }
+
+ //copy
+ tex.copyTo(temp_tex);
+ tex = temp_tex;
+ }
+
+ //create intermediate canvas with lowquality version
+ var tex_canvas = this._preview_canvas;
+ if(!tex_canvas)
+ {
+ tex_canvas = createCanvas(size,size);
+ this._preview_canvas = tex_canvas;
+ }
+
+ if(temp_tex)
+ temp_tex.toCanvas(tex_canvas);
+ return tex_canvas;
+ }
+
+ LGraphTexture.prototype.getResources = function(res)
+ {
+ res[ this.properties.name ] = GL.Texture;
+ return res;
+ }
+
+ LGraphTexture.prototype.onGetInputs = function()
+ {
+ return [["in","Texture"]];
+ }
+
+
+ LGraphTexture.prototype.onGetOutputs = function()
+ {
+ return [["width","number"],["height","number"],["aspect","number"]];
+ }
+
+ LiteGraph.registerNodeType("texture/texture", LGraphTexture );
+
+ //**************************
+ function LGraphTexturePreview()
+ {
+ this.addInput("Texture","Texture");
+ this.properties = { flipY: false };
+ this.size = [LGraphTexture.image_preview_size, LGraphTexture.image_preview_size];
+ }
+
+ LGraphTexturePreview.title = "Preview";
+ LGraphTexturePreview.desc = "Show a texture in the graph canvas";
+ LGraphTexturePreview.allow_preview = false;
+
+ LGraphTexturePreview.prototype.onDrawBackground = function(ctx)
+ {
+ if(this.flags.collapsed)
+ return;
+
+ if(!ctx.webgl && !LGraphTexturePreview.allow_preview)
+ return; //not working well
+
+ var tex = this.getInputData(0);
+ if(!tex)
+ return;
+
+ var tex_canvas = null;
+
+ if(!tex.handle && ctx.webgl)
+ tex_canvas = tex;
+ else
+ tex_canvas = LGraphTexture.generateLowResTexturePreview(tex);
+
+ //render to graph canvas
+ ctx.save();
+ if(this.properties.flipY)
+ {
+ ctx.translate(0,this.size[1]);
+ ctx.scale(1,-1);
+ }
+ ctx.drawImage(tex_canvas,0,0,this.size[0],this.size[1]);
+ ctx.restore();
+ }
+
+ LiteGraph.registerNodeType("texture/preview", LGraphTexturePreview );
+
+ //**************************************
+
+ function LGraphTextureSave()
+ {
+ this.addInput("Texture","Texture");
+ this.addOutput("","Texture");
+ this.properties = {name:""};
+ }
+
+ LGraphTextureSave.title = "Save";
+ LGraphTextureSave.desc = "Save a texture in the repository";
+
+ LGraphTextureSave.prototype.onExecute = function()
+ {
+ var tex = this.getInputData(0);
+ if(!tex)
+ return;
+
+ if(this.properties.name)
+ {
+ //for cases where we want to perform something when storing it
+ if( LGraphTexture.storeTexture )
+ LGraphTexture.storeTexture( this.properties.name, tex );
+ else
+ {
+ var container = LGraphTexture.getTexturesContainer();
+ container[ this.properties.name ] = tex;
+ }
+ }
+
+ this.setOutputData(0, tex);
+ }
+
+ LiteGraph.registerNodeType("texture/save", LGraphTextureSave );
+
+ //****************************************************
+
+ function LGraphTextureOperation()
+ {
+ this.addInput("Texture","Texture");
+ this.addInput("TextureB","Texture");
+ this.addInput("value","number");
+ this.addOutput("Texture","Texture");
+ this.help = "pixelcode must be vec3
\
+ uvcode must be vec2, is optional
\
+ uv: tex. coords
color: texture
colorB: textureB
time: scene time
value: input value
";
+
+ this.properties = {value:1, uvcode:"", pixelcode:"color + colorB * value", precision: LGraphTexture.DEFAULT };
+ }
+
+ LGraphTextureOperation.widgets_info = {
+ "uvcode": { widget:"textarea", height: 100 },
+ "pixelcode": { widget:"textarea", height: 100 },
+ "precision": { widget:"combo", values: LGraphTexture.MODE_VALUES }
+ };
+
+ LGraphTextureOperation.title = "Operation";
+ LGraphTextureOperation.desc = "Texture shader operation";
+
+ LGraphTextureOperation.prototype.getExtraMenuOptions = function(graphcanvas)
+ {
+ var that = this;
+ var txt = !that.properties.show ? "Show Texture" : "Hide Texture";
+ return [ {content: txt, callback:
+ function() {
+ that.properties.show = !that.properties.show;
+ }
+ }];
+ }
+
+ LGraphTextureOperation.prototype.onDrawBackground = function(ctx)
+ {
+ if(this.flags.collapsed || this.size[1] <= 20 || !this.properties.show)
+ return;
+
+ if(!this._tex)
+ return;
+
+ //only works if using a webgl renderer
+ if(this._tex.gl != ctx)
+ return;
+
+ //render to graph canvas
+ ctx.save();
+ ctx.drawImage(this._tex, 0, 0, this.size[0], this.size[1]);
+ ctx.restore();
+ }
+
+ LGraphTextureOperation.prototype.onExecute = function()
+ {
+ var tex = this.getInputData(0);
+
+ if(!this.isOutputConnected(0))
+ return; //saves work
+
+ if(this.properties.precision === LGraphTexture.PASS_THROUGH)
+ {
+ this.setOutputData(0, tex);
+ return;
+ }
+
+ var texB = this.getInputData(1);
+
+ if(!this.properties.uvcode && !this.properties.pixelcode)
+ return;
+
+ var width = 512;
+ var height = 512;
+ if(tex)
+ {
+ width = tex.width;
+ height = tex.height;
+ }
+ else if (texB)
+ {
+ width = texB.width;
+ height = texB.height;
+ }
+
+ var type = LGraphTexture.getTextureType( this.properties.precision, tex );
+
+ if(!tex && !this._tex )
+ this._tex = new GL.Texture( width, height, { type: type, format: gl.RGBA, filter: gl.LINEAR });
+ else
+ this._tex = LGraphTexture.getTargetTexture( tex || this._tex, this._tex, this.properties.precision );
+
+ var uvcode = "";
+ if(this.properties.uvcode)
+ {
+ uvcode = "uv = " + this.properties.uvcode;
+ if(this.properties.uvcode.indexOf(";") != -1) //there are line breaks, means multiline code
+ uvcode = this.properties.uvcode;
+ }
+
+ var pixelcode = "";
+ if(this.properties.pixelcode)
+ {
+ pixelcode = "result = " + this.properties.pixelcode;
+ if(this.properties.pixelcode.indexOf(";") != -1) //there are line breaks, means multiline code
+ pixelcode = this.properties.pixelcode;
+ }
+
+ var shader = this._shader;
+
+ if(!shader || this._shader_code != (uvcode + "|" + pixelcode) )
+ {
+ try
+ {
+ this._shader = new GL.Shader(Shader.SCREEN_VERTEX_SHADER, LGraphTextureOperation.pixel_shader, { UV_CODE: uvcode, PIXEL_CODE: pixelcode });
+ this.boxcolor = "#00FF00";
+ }
+ catch (err)
+ {
+ console.log("Error compiling shader: ", err);
+ this.boxcolor = "#FF0000";
+ return;
+ }
+ this.boxcolor = "#FF0000";
+
+ this._shader_code = (uvcode + "|" + pixelcode);
+ shader = this._shader;
+ }
+
+ if(!shader)
+ {
+ this.boxcolor = "red";
+ return;
+ }
+ else
+ this.boxcolor = "green";
+
+ var value = this.getInputData(2);
+ if(value != null)
+ this.properties.value = value;
+ else
+ value = parseFloat( this.properties.value );
+
+ var time = this.graph.getTime();
+
+ this._tex.drawTo(function() {
+ gl.disable( gl.DEPTH_TEST );
+ gl.disable( gl.CULL_FACE );
+ gl.disable( gl.BLEND );
+ if(tex) tex.bind(0);
+ if(texB) texB.bind(1);
+ var mesh = Mesh.getScreenQuad();
+ shader.uniforms({u_texture:0, u_textureB:1, value: value, texSize:[width,height], time: time}).draw(mesh);
+ });
+
+ this.setOutputData(0, this._tex);
+ }
+
+ LGraphTextureOperation.pixel_shader = "precision highp float;\n\
+ \n\
+ uniform sampler2D u_texture;\n\
+ uniform sampler2D u_textureB;\n\
+ varying vec2 v_coord;\n\
+ uniform vec2 texSize;\n\
+ uniform float time;\n\
+ uniform float value;\n\
+ \n\
+ void main() {\n\
+ vec2 uv = v_coord;\n\
+ UV_CODE;\n\
+ vec4 color4 = texture2D(u_texture, uv);\n\
+ vec3 color = color4.rgb;\n\
+ vec4 color4B = texture2D(u_textureB, uv);\n\
+ vec3 colorB = color4B.rgb;\n\
+ vec3 result = color;\n\
+ float alpha = 1.0;\n\
+ PIXEL_CODE;\n\
+ gl_FragColor = vec4(result, alpha);\n\
+ }\n\
+ ";
+
+ LiteGraph.registerNodeType("texture/operation", LGraphTextureOperation );
+
+ //****************************************************
+
+ function LGraphTextureShader()
+ {
+ this.addOutput("out","Texture");
+ this.properties = {code:"", width: 512, height: 512, precision: LGraphTexture.DEFAULT };
+
+ this.properties.code = "\nvoid main() {\n vec2 uv = v_coord;\n vec3 color = vec3(0.0);\n//your code here\n\ngl_FragColor = vec4(color, 1.0);\n}\n";
+ this._uniforms = { in_texture:0, texSize: vec2.create(), time: 0 };
+ }
+
+ LGraphTextureShader.title = "Shader";
+ LGraphTextureShader.desc = "Texture shader";
+ LGraphTextureShader.widgets_info = {
+ "code": { type:"code" },
+ "precision": { widget:"combo", values: LGraphTexture.MODE_VALUES }
+ };
+
+ LGraphTextureShader.prototype.onPropertyChanged = function(name, value)
+ {
+ if(name != "code")
+ return;
+
+ var shader = this.getShader();
+ if(!shader)
+ return;
+
+ //update connections
+ var uniforms = shader.uniformInfo;
+
+ //remove deprecated slots
+ if(this.inputs)
+ {
+ var already = {};
+ for(var i = 0; i < this.inputs.length; ++i)
+ {
+ var info = this.getInputInfo(i);
+ if(!info)
+ continue;
+
+ if( uniforms[ info.name ] && !already[ info.name ] )
+ {
+ already[ info.name ] = true;
+ continue;
+ }
+ this.removeInput(i);
+ i--;
+ }
+ }
+
+ //update existing ones
+ for(var i in uniforms)
+ {
+ var info = shader.uniformInfo[i];
+ if(info.loc === null)
+ continue; //is an attribute, not a uniform
+ if(i == "time") //default one
+ continue;
+
+ var type = "number";
+ if( this._shader.samplers[i] )
+ type = "texture";
+ else
+ {
+ switch(info.size)
+ {
+ case 1: type = "number"; break;
+ case 2: type = "vec2"; break;
+ case 3: type = "vec3"; break;
+ case 4: type = "vec4"; break;
+ case 9: type = "mat3"; break;
+ case 16: type = "mat4"; break;
+ default: continue;
+ }
+ }
+
+ var slot = this.findInputSlot(i);
+ if(slot == -1)
+ {
+ this.addInput(i,type);
+ continue;
+ }
+
+ var input_info = this.getInputInfo(slot);
+ if(!input_info)
+ this.addInput(i,type);
+ else
+ {
+ if(input_info.type == type)
+ continue;
+ this.removeInput(slot,type);
+ this.addInput(i,type);
+ }
+ }
+ }
+
+ LGraphTextureShader.prototype.getShader = function()
+ {
+ //replug
+ if(this._shader && this._shader_code == this.properties.code)
+ return this._shader;
+
+ this._shader_code = this.properties.code;
+ this._shader = new GL.Shader(Shader.SCREEN_VERTEX_SHADER, LGraphTextureShader.pixel_shader + this.properties.code );
+ if(!this._shader) {
+ this.boxcolor = "red";
+ return null;
+ }
+ else
+ this.boxcolor = "green";
+ return this._shader;
+ }
+
+ LGraphTextureShader.prototype.onExecute = function()
+ {
+ if(!this.isOutputConnected(0))
+ return; //saves work
+
+ var shader = this.getShader();
+ if(!shader)
+ return;
+
+ var tex_slot = 0;
+ var in_tex = null;
+
+ //set uniforms
+ for(var i = 0; i < this.inputs.length; ++i)
+ {
+ var info = this.getInputInfo(i);
+ var data = this.getInputData(i);
+ if(data == null)
+ continue;
+
+ if(data.constructor === GL.Texture)
+ {
+ data.bind(tex_slot);
+ if(!in_tex)
+ in_tex = data;
+ data = tex_slot;
+ tex_slot++;
+ }
+ shader.setUniform( info.name, data ); //data is tex_slot
+ }
+
+ var uniforms = this._uniforms;
+ var type = LGraphTexture.getTextureType( this.properties.precision, in_tex );
+
+ //render to texture
+ var w = this.properties.width|0;
+ var h = this.properties.height|0;
+ if(w == 0)
+ w = in_tex ? in_tex.width : gl.canvas.width;
+ if(h == 0)
+ h = in_tex ? in_tex.height : gl.canvas.height;
+ uniforms.texSize[0] = w;
+ uniforms.texSize[1] = h;
+ uniforms.time = this.graph.getTime();
+
+ if(!this._tex || this._tex.type != type || this._tex.width != w || this._tex.height != h )
+ this._tex = new GL.Texture( w, h, { type: type, format: gl.RGBA, filter: gl.LINEAR });
+ var tex = this._tex;
+ tex.drawTo(function() {
+ shader.uniforms( uniforms ).draw( GL.Mesh.getScreenQuad() );
+ });
+
+ this.setOutputData( 0, this._tex );
+ }
+
+ LGraphTextureShader.pixel_shader = "precision highp float;\n\
+ \n\
+ varying vec2 v_coord;\n\
+ uniform float time;\n\
+ ";
+
+ LiteGraph.registerNodeType("texture/shader", LGraphTextureShader );
+
+ // Texture Scale Offset
+
+ function LGraphTextureScaleOffset()
+ {
+ this.addInput("in","Texture");
+ this.addInput("scale","vec2");
+ this.addInput("offset","vec2");
+ this.addOutput("out","Texture");
+ this.properties = { offset: vec2.fromValues(0,0), scale: vec2.fromValues(1,1), precision: LGraphTexture.DEFAULT };
+ }
+
+ LGraphTextureScaleOffset.widgets_info = {
+ "precision": { widget:"combo", values: LGraphTexture.MODE_VALUES }
+ };
+
+ LGraphTextureScaleOffset.title = "Scale/Offset";
+ LGraphTextureScaleOffset.desc = "Applies an scaling and offseting";
+
+ LGraphTextureScaleOffset.prototype.onExecute = function()
+ {
+ var tex = this.getInputData(0);
+
+ if(!this.isOutputConnected(0) || !tex)
+ return; //saves work
+
+ if(this.properties.precision === LGraphTexture.PASS_THROUGH)
+ {
+ this.setOutputData(0, tex);
+ return;
+ }
+
+ var width = tex.width;
+ var height = tex.height;
+ var type = this.precision === LGraphTexture.LOW ? gl.UNSIGNED_BYTE : gl.HIGH_PRECISION_FORMAT;
+ if (this.precision === LGraphTexture.DEFAULT)
+ type = tex.type;
+
+ if(!this._tex || this._tex.width != width || this._tex.height != height || this._tex.type != type )
+ this._tex = new GL.Texture( width, height, { type: type, format: gl.RGBA, filter: gl.LINEAR });
+
+ var shader = this._shader;
+
+ if(!shader)
+ shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureScaleOffset.pixel_shader );
+
+ var scale = this.getInputData(1);
+ if(scale)
+ {
+ this.properties.scale[0] = scale[0];
+ this.properties.scale[1] = scale[1];
+ }
+ else
+ scale = this.properties.scale;
+
+ var offset = this.getInputData(2);
+ if(offset)
+ {
+ this.properties.offset[0] = offset[0];
+ this.properties.offset[1] = offset[1];
+ }
+ else
+ offset = this.properties.offset;
+
+ this._tex.drawTo(function() {
+ gl.disable( gl.DEPTH_TEST );
+ gl.disable( gl.CULL_FACE );
+ gl.disable( gl.BLEND );
+ tex.bind(0);
+ var mesh = Mesh.getScreenQuad();
+ shader.uniforms({u_texture:0, u_scale: scale, u_offset: offset}).draw( mesh );
+ });
+
+ this.setOutputData( 0, this._tex );
+ }
+
+ LGraphTextureScaleOffset.pixel_shader = "precision highp float;\n\
+ \n\
+ uniform sampler2D u_texture;\n\
+ uniform sampler2D u_textureB;\n\
+ varying vec2 v_coord;\n\
+ uniform vec2 u_scale;\n\
+ uniform vec2 u_offset;\n\
+ \n\
+ void main() {\n\
+ vec2 uv = v_coord;\n\
+ uv = uv / u_scale - u_offset;\n\
+ gl_FragColor = texture2D(u_texture, uv);\n\
+ }\n\
+ ";
+
+ LiteGraph.registerNodeType("texture/scaleOffset", LGraphTextureScaleOffset );
+
+
+
+ // Warp (distort a texture) *************************
+
+ function LGraphTextureWarp()
+ {
+ this.addInput("in","Texture");
+ this.addInput("warp","Texture");
+ this.addInput("factor","number");
+ this.addOutput("out","Texture");
+ this.properties = { factor: 0.01, precision: LGraphTexture.DEFAULT };
+ }
+
+ LGraphTextureWarp.widgets_info = {
+ "precision": { widget:"combo", values: LGraphTexture.MODE_VALUES }
+ };
+
+ LGraphTextureWarp.title = "Warp";
+ LGraphTextureWarp.desc = "Texture warp operation";
+
+ LGraphTextureWarp.prototype.onExecute = function()
+ {
+ var tex = this.getInputData(0);
+
+ if(!this.isOutputConnected(0))
+ return; //saves work
+
+ if(this.properties.precision === LGraphTexture.PASS_THROUGH)
+ {
+ this.setOutputData(0, tex);
+ return;
+ }
+
+ var texB = this.getInputData(1);
+
+ var width = 512;
+ var height = 512;
+ var type = gl.UNSIGNED_BYTE;
+ if(tex)
+ {
+ width = tex.width;
+ height = tex.height;
+ type = tex.type;
+ }
+ else if (texB)
+ {
+ width = texB.width;
+ height = texB.height;
+ type = texB.type;
+ }
+
+ if(!tex && !this._tex )
+ this._tex = new GL.Texture( width, height, { type: this.precision === LGraphTexture.LOW ? gl.UNSIGNED_BYTE : gl.HIGH_PRECISION_FORMAT, format: gl.RGBA, filter: gl.LINEAR });
+ else
+ this._tex = LGraphTexture.getTargetTexture( tex || this._tex, this._tex, this.properties.precision );
+
+ var shader = this._shader;
+
+ if(!shader)
+ shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureWarp.pixel_shader );
+
+ var factor = this.getInputData(2);
+ if(factor != null)
+ this.properties.factor = factor;
+ else
+ factor = parseFloat( this.properties.factor );
+
+ this._tex.drawTo(function() {
+ gl.disable( gl.DEPTH_TEST );
+ gl.disable( gl.CULL_FACE );
+ gl.disable( gl.BLEND );
+ if(tex) tex.bind(0);
+ if(texB) texB.bind(1);
+ var mesh = Mesh.getScreenQuad();
+ shader.uniforms({u_texture:0, u_textureB:1, u_factor: factor }).draw( mesh );
+ });
+
+ this.setOutputData(0, this._tex);
+ }
+
+ LGraphTextureWarp.pixel_shader = "precision highp float;\n\
+ \n\
+ uniform sampler2D u_texture;\n\
+ uniform sampler2D u_textureB;\n\
+ varying vec2 v_coord;\n\
+ uniform float u_factor;\n\
+ \n\
+ void main() {\n\
+ vec2 uv = v_coord;\n\
+ uv += ( texture2D(u_textureB, uv).rg - vec2(0.5)) * u_factor;\n\
+ gl_FragColor = texture2D(u_texture, uv);\n\
+ }\n\
+ ";
+
+ LiteGraph.registerNodeType("texture/warp", LGraphTextureWarp );
+
+ //****************************************************
+
+ // Texture to Viewport *****************************************
+ function LGraphTextureToViewport()
+ {
+ this.addInput("Texture","Texture");
+ this.properties = { additive: false, antialiasing: false, filter: true, disable_alpha: false, gamma: 1.0 };
+ this.size[0] = 130;
+ }
+
+ LGraphTextureToViewport.title = "to Viewport";
+ LGraphTextureToViewport.desc = "Texture to viewport";
+
+ LGraphTextureToViewport.prototype.onExecute = function()
+ {
+ var tex = this.getInputData(0);
+ if(!tex)
+ return;
+
+ if(this.properties.disable_alpha)
+ gl.disable( gl.BLEND );
+ else
+ {
+ gl.enable( gl.BLEND );
+ if(this.properties.additive)
+ gl.blendFunc( gl.SRC_ALPHA, gl.ONE );
+ else
+ gl.blendFunc( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA );
+ }
+
+ gl.disable( gl.DEPTH_TEST );
+ var gamma = this.properties.gamma || 1.0;
+ if( this.isInputConnected(1) )
+ gamma = this.getInputData(1);
+
+ tex.setParameter( gl.TEXTURE_MAG_FILTER, this.properties.filter ? gl.LINEAR : gl.NEAREST );
+
+ if(this.properties.antialiasing)
+ {
+ if(!LGraphTextureToViewport._shader)
+ LGraphTextureToViewport._shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureToViewport.aa_pixel_shader );
+
+ var viewport = gl.getViewport(); //gl.getParameter(gl.VIEWPORT);
+ var mesh = Mesh.getScreenQuad();
+ tex.bind(0);
+ LGraphTextureToViewport._shader.uniforms({u_texture:0, uViewportSize:[tex.width,tex.height], u_igamma: 1 / gamma, inverseVP: [1/tex.width,1/tex.height] }).draw(mesh);
+ }
+ else
+ {
+ if(gamma != 1.0)
+ {
+ if(!LGraphTextureToViewport._gamma_shader)
+ LGraphTextureToViewport._gamma_shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphTextureToViewport.gamma_pixel_shader );
+ tex.toViewport(LGraphTextureToViewport._gamma_shader, { u_texture:0, u_igamma: 1 / gamma });
+ }
+ else
+ tex.toViewport();
+ }
+ }
+
+ LGraphTextureToViewport.prototype.onGetInputs = function()
+ {
+ return [["gamma","number"]];
+ }
+
+ LGraphTextureToViewport.aa_pixel_shader = "precision highp float;\n\
+ precision highp float;\n\
+ varying vec2 v_coord;\n\
+ uniform sampler2D u_texture;\n\
+ uniform vec2 uViewportSize;\n\
+ uniform vec2 inverseVP;\n\
+ uniform float u_igamma;\n\
+ #define FXAA_REDUCE_MIN (1.0/ 128.0)\n\
+ #define FXAA_REDUCE_MUL (1.0 / 8.0)\n\
+ #define FXAA_SPAN_MAX 8.0\n\
+ \n\
+ /* from mitsuhiko/webgl-meincraft based on the code on geeks3d.com */\n\
+ vec4 applyFXAA(sampler2D tex, vec2 fragCoord)\n\
+ {\n\
+ vec4 color = vec4(0.0);\n\
+ /*vec2 inverseVP = vec2(1.0 / uViewportSize.x, 1.0 / uViewportSize.y);*/\n\
+ vec3 rgbNW = texture2D(tex, (fragCoord + vec2(-1.0, -1.0)) * inverseVP).xyz;\n\
+ vec3 rgbNE = texture2D(tex, (fragCoord + vec2(1.0, -1.0)) * inverseVP).xyz;\n\
+ vec3 rgbSW = texture2D(tex, (fragCoord + vec2(-1.0, 1.0)) * inverseVP).xyz;\n\
+ vec3 rgbSE = texture2D(tex, (fragCoord + vec2(1.0, 1.0)) * inverseVP).xyz;\n\
+ vec3 rgbM = texture2D(tex, fragCoord * inverseVP).xyz;\n\
+ vec3 luma = vec3(0.299, 0.587, 0.114);\n\
+ float lumaNW = dot(rgbNW, luma);\n\
+ float lumaNE = dot(rgbNE, luma);\n\
+ float lumaSW = dot(rgbSW, luma);\n\
+ float lumaSE = dot(rgbSE, luma);\n\
+ float lumaM = dot(rgbM, luma);\n\
+ float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE)));\n\
+ float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE)));\n\
+ \n\
+ vec2 dir;\n\
+ dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE));\n\
+ dir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE));\n\
+ \n\
+ float dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) * (0.25 * FXAA_REDUCE_MUL), FXAA_REDUCE_MIN);\n\
+ \n\
+ float rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce);\n\
+ dir = min(vec2(FXAA_SPAN_MAX, FXAA_SPAN_MAX), max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX), dir * rcpDirMin)) * inverseVP;\n\
+ \n\
+ vec3 rgbA = 0.5 * (texture2D(tex, fragCoord * inverseVP + dir * (1.0 / 3.0 - 0.5)).xyz + \n\
+ texture2D(tex, fragCoord * inverseVP + dir * (2.0 / 3.0 - 0.5)).xyz);\n\
+ vec3 rgbB = rgbA * 0.5 + 0.25 * (texture2D(tex, fragCoord * inverseVP + dir * -0.5).xyz + \n\
+ texture2D(tex, fragCoord * inverseVP + dir * 0.5).xyz);\n\
+ \n\
+ //return vec4(rgbA,1.0);\n\
+ float lumaB = dot(rgbB, luma);\n\
+ if ((lumaB < lumaMin) || (lumaB > lumaMax))\n\
+ color = vec4(rgbA, 1.0);\n\
+ else\n\
+ color = vec4(rgbB, 1.0);\n\
+ if(u_igamma != 1.0)\n\
+ color.xyz = pow( color.xyz, vec3(u_igamma) );\n\
+ return color;\n\
+ }\n\
+ \n\
+ void main() {\n\
+ gl_FragColor = applyFXAA( u_texture, v_coord * uViewportSize) ;\n\
+ }\n\
+ ";
+
+ LGraphTextureToViewport.gamma_pixel_shader = "precision highp float;\n\
+ precision highp float;\n\
+ varying vec2 v_coord;\n\
+ uniform sampler2D u_texture;\n\
+ uniform float u_igamma;\n\
+ void main() {\n\
+ vec4 color = texture2D( u_texture, v_coord);\n\
+ color.xyz = pow(color.xyz, vec3(u_igamma) );\n\
+ gl_FragColor = color;\n\
+ }\n\
+ ";
+
+
+ LiteGraph.registerNodeType("texture/toviewport", LGraphTextureToViewport );
+
+
+ // Texture Copy *****************************************
+ function LGraphTextureCopy()
+ {
+ this.addInput("Texture","Texture");
+ this.addOutput("","Texture");
+ this.properties = { size: 0, generate_mipmaps: false, precision: LGraphTexture.DEFAULT };
+ }
+
+ LGraphTextureCopy.title = "Copy";
+ LGraphTextureCopy.desc = "Copy Texture";
+ LGraphTextureCopy.widgets_info = {
+ size: { widget:"combo", values:[0,32,64,128,256,512,1024,2048]},
+ precision: { widget:"combo", values: LGraphTexture.MODE_VALUES }
+ };
+
+ LGraphTextureCopy.prototype.onExecute = function()
+ {
+ var tex = this.getInputData(0);
+ if(!tex && !this._temp_texture)
+ return;
+
+ if(!this.isOutputConnected(0))
+ return; //saves work
+
+ //copy the texture
+ if(tex)
+ {
+ var width = tex.width;
+ var height = tex.height;
+
+ if(this.properties.size != 0)
+ {
+ width = this.properties.size;
+ height = this.properties.size;
+ }
+
+ var temp = this._temp_texture;
+
+ var type = tex.type;
+ if(this.properties.precision === LGraphTexture.LOW)
+ type = gl.UNSIGNED_BYTE;
+ else if(this.properties.precision === LGraphTexture.HIGH)
+ type = gl.HIGH_PRECISION_FORMAT;
+
+ if(!temp || temp.width != width || temp.height != height || temp.type != type )
+ {
+ var minFilter = gl.LINEAR;
+ if( this.properties.generate_mipmaps && isPowerOfTwo(width) && isPowerOfTwo(height) )
+ minFilter = gl.LINEAR_MIPMAP_LINEAR;
+ this._temp_texture = new GL.Texture( width, height, { type: type, format: gl.RGBA, minFilter: minFilter, magFilter: gl.LINEAR });
+ }
+ tex.copyTo(this._temp_texture);
+
+ if(this.properties.generate_mipmaps)
+ {
+ this._temp_texture.bind(0);
+ gl.generateMipmap(this._temp_texture.texture_type);
+ this._temp_texture.unbind(0);
+ }
+ }
+
+
+ this.setOutputData(0,this._temp_texture);
+ }
+
+ LiteGraph.registerNodeType("texture/copy", LGraphTextureCopy );
+
+
+ // Texture Downsample *****************************************
+ function LGraphTextureDownsample()
+ {
+ this.addInput("Texture","Texture");
+ this.addOutput("","Texture");
+ this.properties = { iterations: 1, generate_mipmaps: false, precision: LGraphTexture.DEFAULT };
+ }
+
+ LGraphTextureDownsample.title = "Downsample";
+ LGraphTextureDownsample.desc = "Downsample Texture";
+ LGraphTextureDownsample.widgets_info = {
+ iterations: { type:"number", step: 1, precision: 0, min: 1 },
+ precision: { widget:"combo", values: LGraphTexture.MODE_VALUES }
+ };
+
+ LGraphTextureDownsample.prototype.onExecute = function()
+ {
+ var tex = this.getInputData(0);
+ if(!tex && !this._temp_texture)
+ return;
+
+ if(!this.isOutputConnected(0))
+ return; //saves work
+
+ //we do not allow any texture different than texture 2D
+ if(!tex || tex.texture_type !== GL.TEXTURE_2D )
+ return;
+
+ var shader = LGraphTextureDownsample._shader;
+ if(!shader)
+ LGraphTextureDownsample._shader = shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureDownsample.pixel_shader );
+
+ var width = tex.width|0;
+ var height = tex.height|0;
+ var type = tex.type;
+ if(this.properties.precision === LGraphTexture.LOW)
+ type = gl.UNSIGNED_BYTE;
+ else if(this.properties.precision === LGraphTexture.HIGH)
+ type = gl.HIGH_PRECISION_FORMAT;
+ var iterations = this.properties.iterations || 1;
+
+ var origin = tex;
+ var target = null;
+
+ var temp = [];
+ var options = {
+ type: type,
+ format: tex.format
+ };
+
+ var offset = vec2.create();
+ var uniforms = {
+ u_offset: offset
+ };
+
+ if( this._texture )
+ GL.Texture.releaseTemporary( this._texture );
+
+ for(var i = 0; i < iterations; ++i)
+ {
+ offset[0] = 1/width;
+ offset[1] = 1/height;
+ width = width>>1 || 0;
+ height = height>>1 || 0;
+ target = GL.Texture.getTemporary( width, height, options );
+ temp.push( target );
+ origin.setParameter( GL.TEXTURE_MAG_FILTER, GL.NEAREST );
+ origin.copyTo( target, shader, uniforms );
+ if(width == 1 && height == 1)
+ break; //nothing else to do
+ origin = target;
+ }
+
+ //keep the last texture used
+ this._texture = temp.pop();
+
+ //free the rest
+ for(var i = 0; i < temp.length; ++i)
+ GL.Texture.releaseTemporary( temp[i] );
+
+ if(this.properties.generate_mipmaps)
+ {
+ this._texture.bind(0);
+ gl.generateMipmap(this._texture.texture_type);
+ this._texture.unbind(0);
+ }
+
+ this.setOutputData(0,this._texture);
+ }
+
+ LGraphTextureDownsample.pixel_shader = "precision highp float;\n\
+ precision highp float;\n\
+ uniform sampler2D u_texture;\n\
+ uniform vec2 u_offset;\n\
+ varying vec2 v_coord;\n\
+ \n\
+ void main() {\n\
+ vec4 color = texture2D(u_texture, v_coord );\n\
+ color += texture2D(u_texture, v_coord + vec2( u_offset.x, 0.0 ) );\n\
+ color += texture2D(u_texture, v_coord + vec2( 0.0, u_offset.y ) );\n\
+ color += texture2D(u_texture, v_coord + vec2( u_offset.x, u_offset.y ) );\n\
+ gl_FragColor = color * 0.25;\n\
+ }\n\
+ ";
+
+ LiteGraph.registerNodeType("texture/downsample", LGraphTextureDownsample );
+
+
+
+ // Texture Copy *****************************************
+ function LGraphTextureAverage()
+ {
+ this.addInput("Texture","Texture");
+ this.addOutput("tex","Texture");
+ this.addOutput("avg","vec4");
+ this.addOutput("lum","number");
+ this.properties = { mipmap_offset: 0, low_precision: false };
+
+ this._uniforms = { u_texture: 0, u_mipmap_offset: this.properties.mipmap_offset };
+ this._luminance = new Float32Array(4);
+ }
+
+ LGraphTextureAverage.title = "Average";
+ LGraphTextureAverage.desc = "Compute a partial average (32 random samples) of a texture and stores it as a 1x1 pixel texture";
+
+ LGraphTextureAverage.prototype.onExecute = function()
+ {
+ var tex = this.getInputData(0);
+ if(!tex)
+ return;
+
+ if(!this.isOutputConnected(0) && !this.isOutputConnected(1) && !this.isOutputConnected(2))
+ return; //saves work
+
+ if(!LGraphTextureAverage._shader)
+ {
+ LGraphTextureAverage._shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureAverage.pixel_shader);
+ //creates 32 random numbers and stores the, in two mat4
+ var samples = new Float32Array(32);
+ for(var i = 0; i < 32; ++i)
+ samples[i] = Math.random();
+ LGraphTextureAverage._shader.uniforms({u_samples_a: samples.subarray(0,16), u_samples_b: samples.subarray(16,32) });
+ }
+
+ var temp = this._temp_texture;
+ var type = gl.UNSIGNED_BYTE;
+ if(tex.type != type) //force floats, half floats cannot be read with gl.readPixels
+ type = gl.FLOAT;
+
+ if(!temp || temp.type != type )
+ this._temp_texture = new GL.Texture( 1, 1, { type: type, format: gl.RGBA, filter: gl.NEAREST });
+
+ var shader = LGraphTextureAverage._shader;
+ var uniforms = this._uniforms;
+ uniforms.u_mipmap_offset = this.properties.mipmap_offset;
+ this._temp_texture.drawTo(function(){
+ tex.toViewport( shader, uniforms );
+ });
+
+ this.setOutputData(0,this._temp_texture);
+
+ if(this.isOutputConnected(1) || this.isOutputConnected(2))
+ {
+ var pixel = this._temp_texture.getPixels();
+ if(pixel)
+ {
+ var v = this._luminance;
+ var type = this._temp_texture.type;
+ v.set( pixel );
+ if(type == gl.UNSIGNED_BYTE)
+ vec4.scale( v,v, 1/255 );
+ else if(type == GL.HALF_FLOAT || type == GL.HALF_FLOAT_OES)
+ vec4.scale( v,v, 1/(255*255) ); //is this correct?
+ this.setOutputData(1,v);
+ this.setOutputData(2,(v[0] + v[1] + v[2]) / 3);
+ }
+
+ }
+ }
+
+ LGraphTextureAverage.pixel_shader = "precision highp float;\n\
+ precision highp float;\n\
+ uniform mat4 u_samples_a;\n\
+ uniform mat4 u_samples_b;\n\
+ uniform sampler2D u_texture;\n\
+ uniform float u_mipmap_offset;\n\
+ varying vec2 v_coord;\n\
+ \n\
+ void main() {\n\
+ vec4 color = vec4(0.0);\n\
+ for(int i = 0; i < 4; ++i)\n\
+ for(int j = 0; j < 4; ++j)\n\
+ {\n\
+ color += texture2D(u_texture, vec2( u_samples_a[i][j], u_samples_b[i][j] ), u_mipmap_offset );\n\
+ color += texture2D(u_texture, vec2( 1.0 - u_samples_a[i][j], 1.0 - u_samples_b[i][j] ), u_mipmap_offset );\n\
+ }\n\
+ gl_FragColor = color * 0.03125;\n\
+ }\n\
+ ";
+
+ LiteGraph.registerNodeType("texture/average", LGraphTextureAverage );
+
+ // Image To Texture *****************************************
+ function LGraphImageToTexture()
+ {
+ this.addInput("Image","image");
+ this.addOutput("","Texture");
+ this.properties = {};
+ }
+
+ LGraphImageToTexture.title = "Image to Texture";
+ LGraphImageToTexture.desc = "Uploads an image to the GPU";
+ //LGraphImageToTexture.widgets_info = { size: { widget:"combo", values:[0,32,64,128,256,512,1024,2048]} };
+
+ LGraphImageToTexture.prototype.onExecute = function()
+ {
+ var img = this.getInputData(0);
+ if(!img)
+ return;
+
+ var width = img.videoWidth || img.width;
+ var height = img.videoHeight || img.height;
+
+ //this is in case we are using a webgl canvas already, no need to reupload it
+ if(img.gltexture)
+ {
+ this.setOutputData(0,img.gltexture);
+ return;
+ }
+
+
+ var temp = this._temp_texture;
+ if(!temp || temp.width != width || temp.height != height )
+ this._temp_texture = new GL.Texture( width, height, { format: gl.RGBA, filter: gl.LINEAR });
+
+ try
+ {
+ this._temp_texture.uploadImage(img);
+ }
+ catch(err)
+ {
+ console.error("image comes from an unsafe location, cannot be uploaded to webgl: " + err);
+ return;
+ }
+
+ this.setOutputData(0,this._temp_texture);
+ }
+
+ LiteGraph.registerNodeType("texture/imageToTexture", LGraphImageToTexture );
+
+
+ // Texture LUT *****************************************
+ function LGraphTextureLUT()
+ {
+ this.addInput("Texture","Texture");
+ this.addInput("LUT","Texture");
+ this.addInput("Intensity","number");
+ this.addOutput("","Texture");
+ this.properties = { intensity: 1, precision: LGraphTexture.DEFAULT, texture: null };
+
+ if(!LGraphTextureLUT._shader)
+ LGraphTextureLUT._shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphTextureLUT.pixel_shader );
+ }
+
+ LGraphTextureLUT.widgets_info = {
+ "texture": { widget:"texture"},
+ "precision": { widget:"combo", values: LGraphTexture.MODE_VALUES }
+ };
+
+ LGraphTextureLUT.title = "LUT";
+ LGraphTextureLUT.desc = "Apply LUT to Texture";
+
+ LGraphTextureLUT.prototype.onExecute = function()
+ {
+ if(!this.isOutputConnected(0))
+ return; //saves work
+
+ var tex = this.getInputData(0);
+
+ if(this.properties.precision === LGraphTexture.PASS_THROUGH )
+ {
+ this.setOutputData(0,tex);
+ return;
+ }
+
+ if(!tex)
+ return;
+
+ var lut_tex = this.getInputData(1);
+
+ if(!lut_tex)
+ lut_tex = LGraphTexture.getTexture( this.properties.texture );
+
+ if(!lut_tex)
+ {
+ this.setOutputData(0,tex);
+ return;
+ }
+
+ lut_tex.bind(0);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR );
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE );
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE );
+ gl.bindTexture(gl.TEXTURE_2D, null);
+
+ var intensity = this.properties.intensity;
+ if( this.isInputConnected(2) )
+ this.properties.intensity = intensity = this.getInputData(2);
+
+ this._tex = LGraphTexture.getTargetTexture( tex, this._tex, this.properties.precision );
+
+ //var mesh = Mesh.getScreenQuad();
+
+ this._tex.drawTo(function() {
+ lut_tex.bind(1);
+ tex.toViewport( LGraphTextureLUT._shader, {u_texture:0, u_textureB:1, u_amount: intensity} );
+ });
+
+ this.setOutputData(0,this._tex);
+ }
+
+ LGraphTextureLUT.pixel_shader = "precision highp float;\n\
+ precision highp float;\n\
+ varying vec2 v_coord;\n\
+ uniform sampler2D u_texture;\n\
+ uniform sampler2D u_textureB;\n\
+ uniform float u_amount;\n\
+ \n\
+ void main() {\n\
+ lowp vec4 textureColor = clamp( texture2D(u_texture, v_coord), vec4(0.0), vec4(1.0) );\n\
+ mediump float blueColor = textureColor.b * 63.0;\n\
+ mediump vec2 quad1;\n\
+ quad1.y = floor(floor(blueColor) / 8.0);\n\
+ quad1.x = floor(blueColor) - (quad1.y * 8.0);\n\
+ mediump vec2 quad2;\n\
+ quad2.y = floor(ceil(blueColor) / 8.0);\n\
+ quad2.x = ceil(blueColor) - (quad2.y * 8.0);\n\
+ highp vec2 texPos1;\n\
+ texPos1.x = (quad1.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r);\n\
+ texPos1.y = 1.0 - ((quad1.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.g));\n\
+ highp vec2 texPos2;\n\
+ texPos2.x = (quad2.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r);\n\
+ texPos2.y = 1.0 - ((quad2.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.g));\n\
+ lowp vec4 newColor1 = texture2D(u_textureB, texPos1);\n\
+ lowp vec4 newColor2 = texture2D(u_textureB, texPos2);\n\
+ lowp vec4 newColor = mix(newColor1, newColor2, fract(blueColor));\n\
+ gl_FragColor = vec4( mix( textureColor.rgb, newColor.rgb, u_amount), textureColor.w);\n\
+ }\n\
+ ";
+
+ LiteGraph.registerNodeType("texture/LUT", LGraphTextureLUT );
+
+ // Texture Channels *****************************************
+ function LGraphTextureChannels()
+ {
+ this.addInput("Texture","Texture");
+
+ this.addOutput("R","Texture");
+ this.addOutput("G","Texture");
+ this.addOutput("B","Texture");
+ this.addOutput("A","Texture");
+
+ this.properties = {};
+ if(!LGraphTextureChannels._shader)
+ LGraphTextureChannels._shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphTextureChannels.pixel_shader );
+ }
+
+ LGraphTextureChannels.title = "Texture to Channels";
+ LGraphTextureChannels.desc = "Split texture channels";
+
+ LGraphTextureChannels.prototype.onExecute = function()
+ {
+ var texA = this.getInputData(0);
+ if(!texA) return;
+
+ if(!this._channels)
+ this._channels = Array(4);
+
+ var connections = 0;
+ for(var i = 0; i < 4; i++)
+ {
+ if(this.isOutputConnected(i))
+ {
+ if(!this._channels[i] || this._channels[i].width != texA.width || this._channels[i].height != texA.height || this._channels[i].type != texA.type)
+ this._channels[i] = new GL.Texture( texA.width, texA.height, { type: texA.type, format: gl.RGBA, filter: gl.LINEAR });
+ connections++;
+ }
+ else
+ this._channels[i] = null;
+ }
+
+ if(!connections)
+ return;
+
+ gl.disable( gl.BLEND );
+ gl.disable( gl.DEPTH_TEST );
+
+ var mesh = Mesh.getScreenQuad();
+ var shader = LGraphTextureChannels._shader;
+ var masks = [[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]];
+
+ for(var i = 0; i < 4; i++)
+ {
+ if(!this._channels[i])
+ continue;
+
+ this._channels[i].drawTo( function() {
+ texA.bind(0);
+ shader.uniforms({u_texture:0, u_mask: masks[i]}).draw(mesh);
+ });
+ this.setOutputData(i, this._channels[i]);
+ }
+ }
+
+ LGraphTextureChannels.pixel_shader = "precision highp float;\n\
+ precision highp float;\n\
+ varying vec2 v_coord;\n\
+ uniform sampler2D u_texture;\n\
+ uniform vec4 u_mask;\n\
+ \n\
+ void main() {\n\
+ gl_FragColor = vec4( vec3( length( texture2D(u_texture, v_coord) * u_mask )), 1.0 );\n\
+ }\n\
+ ";
+
+ LiteGraph.registerNodeType("texture/textureChannels", LGraphTextureChannels );
+
+
+ // Texture Channels to Texture *****************************************
+ function LGraphChannelsTexture()
+ {
+ this.addInput("R","Texture");
+ this.addInput("G","Texture");
+ this.addInput("B","Texture");
+ this.addInput("A","Texture");
+
+ this.addOutput("Texture","Texture");
+
+ this.properties = {};
+ if(!LGraphChannelsTexture._shader)
+ LGraphChannelsTexture._shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphChannelsTexture.pixel_shader );
+ }
+
+ LGraphChannelsTexture.title = "Channels to Texture";
+ LGraphChannelsTexture.desc = "Split texture channels";
+
+ LGraphChannelsTexture.prototype.onExecute = function()
+ {
+ var tex = [ this.getInputData(0),
+ this.getInputData(1),
+ this.getInputData(2),
+ this.getInputData(3) ];
+
+ if(!tex[0] || !tex[1] || !tex[2] || !tex[3])
+ return;
+
+ gl.disable( gl.BLEND );
+ gl.disable( gl.DEPTH_TEST );
+
+ var mesh = Mesh.getScreenQuad();
+ var shader = LGraphChannelsTexture._shader;
+
+ this._tex = LGraphTexture.getTargetTexture( tex[0], this._tex );
+
+ this._tex.drawTo( function() {
+ tex[0].bind(0);
+ tex[1].bind(1);
+ tex[2].bind(2);
+ tex[3].bind(3);
+ shader.uniforms({u_textureR:0, u_textureG:1, u_textureB:2, u_textureA:3 }).draw(mesh);
+ });
+ this.setOutputData(0, this._tex);
+ }
+
+ LGraphChannelsTexture.pixel_shader = "precision highp float;\n\
+ precision highp float;\n\
+ varying vec2 v_coord;\n\
+ uniform sampler2D u_textureR;\n\
+ uniform sampler2D u_textureG;\n\
+ uniform sampler2D u_textureB;\n\
+ uniform sampler2D u_textureA;\n\
+ \n\
+ void main() {\n\
+ gl_FragColor = vec4( \
+ texture2D(u_textureR, v_coord).r,\
+ texture2D(u_textureG, v_coord).r,\
+ texture2D(u_textureB, v_coord).r,\
+ texture2D(u_textureA, v_coord).r);\n\
+ }\n\
+ ";
+
+ LiteGraph.registerNodeType("texture/channelsTexture", LGraphChannelsTexture );
+
+ // Texture Channels to Texture *****************************************
+ function LGraphTextureGradient()
+ {
+ this.addInput("A","color");
+ this.addInput("B","color");
+ this.addOutput("Texture","Texture");
+
+ this.properties = { angle: 0, scale: 1, A:[0,0,0], B:[1,1,1], texture_size:32 };
+ if(!LGraphTextureGradient._shader)
+ LGraphTextureGradient._shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphTextureGradient.pixel_shader );
+
+ this._uniforms = { u_angle: 0, u_colorA: vec3.create(), u_colorB: vec3.create()};
+ }
+
+ LGraphTextureGradient.title = "Gradient";
+ LGraphTextureGradient.desc = "Generates a gradient";
+ LGraphTextureGradient["@A"] = { type:"color" };
+ LGraphTextureGradient["@B"] = { type:"color" };
+ LGraphTextureGradient["@texture_size"] = { type:"enum", values:[32,64,128,256,512] };
+
+ LGraphTextureGradient.prototype.onExecute = function()
+ {
+ gl.disable( gl.BLEND );
+ gl.disable( gl.DEPTH_TEST );
+
+ var mesh = GL.Mesh.getScreenQuad();
+ var shader = LGraphTextureGradient._shader;
+
+ var A = this.getInputData(0);
+ if(!A)
+ A = this.properties.A;
+ var B = this.getInputData(1);
+ if(!B)
+ B = this.properties.B;
+
+ //angle and scale
+ for(var i = 2; i < this.inputs.length; i++)
+ {
+ var input = this.inputs[i];
+ var v = this.getInputData(i);
+ if(v === undefined)
+ continue;
+ this.properties[ input.name ] = v;
+ }
+
+ var uniforms = this._uniforms;
+ this._uniforms.u_angle = this.properties.angle * DEG2RAD;
+ this._uniforms.u_scale = this.properties.scale;
+ vec3.copy( uniforms.u_colorA, A );
+ vec3.copy( uniforms.u_colorB, B );
+
+ var size = parseInt( this.properties.texture_size );
+ if(!this._tex || this._tex.width != size )
+ this._tex = new GL.Texture( size, size, { format: gl.RGB, filter: gl.LINEAR });
+
+ this._tex.drawTo( function() {
+ shader.uniforms(uniforms).draw(mesh);
+ });
+ this.setOutputData(0, this._tex);
+ }
+
+ LGraphTextureGradient.prototype.onGetInputs = function()
+ {
+ return [["angle","number"],["scale","number"]];
+ }
+
+ LGraphTextureGradient.pixel_shader = "precision highp float;\n\
+ precision highp float;\n\
+ varying vec2 v_coord;\n\
+ uniform float u_angle;\n\
+ uniform float u_scale;\n\
+ uniform vec3 u_colorA;\n\
+ uniform vec3 u_colorB;\n\
+ \n\
+ vec2 rotate(vec2 v, float angle)\n\
+ {\n\
+ vec2 result;\n\
+ float _cos = cos(angle);\n\
+ float _sin = sin(angle);\n\
+ result.x = v.x * _cos - v.y * _sin;\n\
+ result.y = v.x * _sin + v.y * _cos;\n\
+ return result;\n\
+ }\n\
+ void main() {\n\
+ float f = (rotate(u_scale * (v_coord - vec2(0.5)), u_angle) + vec2(0.5)).x;\n\
+ vec3 color = mix(u_colorA,u_colorB,clamp(f,0.0,1.0));\n\
+ gl_FragColor = vec4(color,1.0);\n\
+ }\n\
+ ";
+
+ LiteGraph.registerNodeType("texture/gradient", LGraphTextureGradient );
+
+ // Texture Mix *****************************************
+ function LGraphTextureMix()
+ {
+ this.addInput("A","Texture");
+ this.addInput("B","Texture");
+ this.addInput("Mixer","Texture");
+
+ this.addOutput("Texture","Texture");
+ this.properties = { precision: LGraphTexture.DEFAULT };
+
+ if(!LGraphTextureMix._shader)
+ LGraphTextureMix._shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphTextureMix.pixel_shader );
+ }
+
+ LGraphTextureMix.title = "Mix";
+ LGraphTextureMix.desc = "Generates a texture mixing two textures";
+
+ LGraphTextureMix.widgets_info = {
+ "precision": { widget:"combo", values: LGraphTexture.MODE_VALUES }
+ };
+
+ LGraphTextureMix.prototype.onExecute = function()
+ {
+ var texA = this.getInputData(0);
+
+ if(!this.isOutputConnected(0))
+ return; //saves work
+
+ if(this.properties.precision === LGraphTexture.PASS_THROUGH )
+ {
+ this.setOutputData(0,texA);
+ return;
+ }
+
+ var texB = this.getInputData(1);
+ var texMix = this.getInputData(2);
+ if(!texA || !texB || !texMix) return;
+
+ this._tex = LGraphTexture.getTargetTexture( texA, this._tex, this.properties.precision );
+
+ gl.disable( gl.BLEND );
+ gl.disable( gl.DEPTH_TEST );
+
+ var mesh = Mesh.getScreenQuad();
+ var shader = LGraphTextureMix._shader;
+
+ this._tex.drawTo( function() {
+ texA.bind(0);
+ texB.bind(1);
+ texMix.bind(2);
+ shader.uniforms({u_textureA:0,u_textureB:1,u_textureMix:2}).draw(mesh);
+ });
+
+ this.setOutputData(0, this._tex);
+ }
+
+ LGraphTextureMix.pixel_shader = "precision highp float;\n\
+ precision highp float;\n\
+ varying vec2 v_coord;\n\
+ uniform sampler2D u_textureA;\n\
+ uniform sampler2D u_textureB;\n\
+ uniform sampler2D u_textureMix;\n\
+ \n\
+ void main() {\n\
+ gl_FragColor = mix( texture2D(u_textureA, v_coord), texture2D(u_textureB, v_coord), texture2D(u_textureMix, v_coord) );\n\
+ }\n\
+ ";
+
+ LiteGraph.registerNodeType("texture/mix", LGraphTextureMix );
+
+ // Texture Edges detection *****************************************
+ function LGraphTextureEdges()
+ {
+ this.addInput("Tex.","Texture");
+
+ this.addOutput("Edges","Texture");
+ this.properties = { invert: true, threshold: false, factor: 1, precision: LGraphTexture.DEFAULT };
+
+ if(!LGraphTextureEdges._shader)
+ LGraphTextureEdges._shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphTextureEdges.pixel_shader );
+ }
+
+ LGraphTextureEdges.title = "Edges";
+ LGraphTextureEdges.desc = "Detects edges";
+
+ LGraphTextureEdges.widgets_info = {
+ "precision": { widget:"combo", values: LGraphTexture.MODE_VALUES }
+ };
+
+ LGraphTextureEdges.prototype.onExecute = function()
+ {
+ if(!this.isOutputConnected(0))
+ return; //saves work
+
+ var tex = this.getInputData(0);
+
+ if(this.properties.precision === LGraphTexture.PASS_THROUGH )
+ {
+ this.setOutputData(0,tex);
+ return;
+ }
+
+ if(!tex) return;
+
+ this._tex = LGraphTexture.getTargetTexture( tex, this._tex, this.properties.precision );
+
+ gl.disable( gl.BLEND );
+ gl.disable( gl.DEPTH_TEST );
+
+ var mesh = Mesh.getScreenQuad();
+ var shader = LGraphTextureEdges._shader;
+ var invert = this.properties.invert;
+ var factor = this.properties.factor;
+ var threshold = this.properties.threshold ? 1 : 0;
+
+ this._tex.drawTo( function() {
+ tex.bind(0);
+ shader.uniforms({u_texture:0, u_isize:[1/tex.width,1/tex.height], u_factor: factor, u_threshold: threshold, u_invert: invert ? 1 : 0}).draw(mesh);
+ });
+
+ this.setOutputData(0, this._tex);
+ }
+
+ LGraphTextureEdges.pixel_shader = "precision highp float;\n\
+ precision highp float;\n\
+ varying vec2 v_coord;\n\
+ uniform sampler2D u_texture;\n\
+ uniform vec2 u_isize;\n\
+ uniform int u_invert;\n\
+ uniform float u_factor;\n\
+ uniform float u_threshold;\n\
+ \n\
+ void main() {\n\
+ vec4 center = texture2D(u_texture, v_coord);\n\
+ vec4 up = texture2D(u_texture, v_coord + u_isize * vec2(0.0,1.0) );\n\
+ vec4 down = texture2D(u_texture, v_coord + u_isize * vec2(0.0,-1.0) );\n\
+ vec4 left = texture2D(u_texture, v_coord + u_isize * vec2(1.0,0.0) );\n\
+ vec4 right = texture2D(u_texture, v_coord + u_isize * vec2(-1.0,0.0) );\n\
+ vec4 diff = abs(center - up) + abs(center - down) + abs(center - left) + abs(center - right);\n\
+ diff *= u_factor;\n\
+ if(u_invert == 1)\n\
+ diff.xyz = vec3(1.0) - diff.xyz;\n\
+ if( u_threshold == 0.0 )\n\
+ gl_FragColor = vec4( diff.xyz, center.a );\n\
+ else\n\
+ gl_FragColor = vec4( diff.x > 0.5 ? 1.0 : 0.0, diff.y > 0.5 ? 1.0 : 0.0, diff.z > 0.5 ? 1.0 : 0.0, center.a );\n\
+ }\n\
+ ";
+
+ LiteGraph.registerNodeType("texture/edges", LGraphTextureEdges );
+
+ // Texture Depth *****************************************
+ function LGraphTextureDepthRange()
+ {
+ this.addInput("Texture","Texture");
+ this.addInput("Distance","number");
+ this.addInput("Range","number");
+ this.addOutput("Texture","Texture");
+ this.properties = { distance:100, range: 50, only_depth: false, high_precision: false };
+ this._uniforms = {u_texture:0, u_distance: 100, u_range: 50, u_camera_planes: null };
+ }
+
+ LGraphTextureDepthRange.title = "Depth Range";
+ LGraphTextureDepthRange.desc = "Generates a texture with a depth range";
+
+ LGraphTextureDepthRange.prototype.onExecute = function()
+ {
+ if(!this.isOutputConnected(0))
+ return; //saves work
+
+ var tex = this.getInputData(0);
+ if(!tex) return;
+
+ var precision = gl.UNSIGNED_BYTE;
+ if(this.properties.high_precision)
+ precision = gl.half_float_ext ? gl.HALF_FLOAT_OES : gl.FLOAT;
+
+ if(!this._temp_texture || this._temp_texture.type != precision ||
+ this._temp_texture.width != tex.width || this._temp_texture.height != tex.height)
+ this._temp_texture = new GL.Texture( tex.width, tex.height, { type: precision, format: gl.RGBA, filter: gl.LINEAR });
+
+ var uniforms = this._uniforms;
+
+ //iterations
+ var distance = this.properties.distance;
+ if( this.isInputConnected(1) )
+ {
+ distance = this.getInputData(1);
+ this.properties.distance = distance;
+ }
+
+ var range = this.properties.range;
+ if( this.isInputConnected(2) )
+ {
+ range = this.getInputData(2);
+ this.properties.range = range;
+ }
+
+ uniforms.u_distance = distance;
+ uniforms.u_range = range;
+
+ gl.disable( gl.BLEND );
+ gl.disable( gl.DEPTH_TEST );
+ var mesh = Mesh.getScreenQuad();
+ if(!LGraphTextureDepthRange._shader)
+ {
+ LGraphTextureDepthRange._shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphTextureDepthRange.pixel_shader );
+ LGraphTextureDepthRange._shader_onlydepth = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphTextureDepthRange.pixel_shader, { ONLY_DEPTH:""} );
+ }
+ var shader = this.properties.only_depth ? LGraphTextureDepthRange._shader_onlydepth : LGraphTextureDepthRange._shader;
+
+ //NEAR AND FAR PLANES
+ var planes = null;
+ if( tex.near_far_planes )
+ planes = tex.near_far_planes;
+ else if( window.LS && LS.Renderer._main_camera )
+ planes = LS.Renderer._main_camera._uniforms.u_camera_planes;
+ else
+ planes = [0.1,1000]; //hardcoded
+ uniforms.u_camera_planes = planes;
+
+
+ this._temp_texture.drawTo( function() {
+ tex.bind(0);
+ shader.uniforms( uniforms ).draw(mesh);
+ });
+
+ this._temp_texture.near_far_planes = planes;
+ this.setOutputData(0, this._temp_texture );
+ }
+
+ LGraphTextureDepthRange.pixel_shader = "precision highp float;\n\
+ precision highp float;\n\
+ varying vec2 v_coord;\n\
+ uniform sampler2D u_texture;\n\
+ uniform vec2 u_camera_planes;\n\
+ uniform float u_distance;\n\
+ uniform float u_range;\n\
+ \n\
+ float LinearDepth()\n\
+ {\n\
+ float zNear = u_camera_planes.x;\n\
+ float zFar = u_camera_planes.y;\n\
+ float depth = texture2D(u_texture, v_coord).x;\n\
+ depth = depth * 2.0 - 1.0;\n\
+ return zNear * (depth + 1.0) / (zFar + zNear - depth * (zFar - zNear));\n\
+ }\n\
+ \n\
+ void main() {\n\
+ float depth = LinearDepth();\n\
+ #ifdef ONLY_DEPTH\n\
+ gl_FragColor = vec4(depth);\n\
+ #else\n\
+ float diff = abs(depth * u_camera_planes.y - u_distance);\n\
+ float dof = 1.0;\n\
+ if(diff <= u_range)\n\
+ dof = diff / u_range;\n\
+ gl_FragColor = vec4(dof);\n\
+ #endif\n\
+ }\n\
+ ";
+
+ LiteGraph.registerNodeType("texture/depth_range", LGraphTextureDepthRange );
+
+ // Texture Blur *****************************************
+ function LGraphTextureBlur()
+ {
+ this.addInput("Texture","Texture");
+ this.addInput("Iterations","number");
+ this.addInput("Intensity","number");
+ this.addOutput("Blurred","Texture");
+ this.properties = { intensity: 1, iterations: 1, preserve_aspect: false, scale:[1,1], precision: LGraphTexture.DEFAULT };
+ }
+
+ LGraphTextureBlur.title = "Blur";
+ LGraphTextureBlur.desc = "Blur a texture";
+
+ LGraphTextureBlur.widgets_info = {
+ precision: { widget:"combo", values: LGraphTexture.MODE_VALUES }
+ };
+
+ LGraphTextureBlur.max_iterations = 20;
+
+ LGraphTextureBlur.prototype.onExecute = function()
+ {
+ var tex = this.getInputData(0);
+ if(!tex)
+ return;
+
+ if(!this.isOutputConnected(0))
+ return; //saves work
+
+ var temp = this._final_texture;
+
+ if(!temp || temp.width != tex.width || temp.height != tex.height || temp.type != tex.type )
+ {
+ //we need two textures to do the blurring
+ //this._temp_texture = new GL.Texture( tex.width, tex.height, { type: tex.type, format: gl.RGBA, filter: gl.LINEAR });
+ temp = this._final_texture = new GL.Texture( tex.width, tex.height, { type: tex.type, format: gl.RGBA, filter: gl.LINEAR });
+ }
+
+ //iterations
+ var iterations = this.properties.iterations;
+ if( this.isInputConnected(1) )
+ {
+ iterations = this.getInputData(1);
+ this.properties.iterations = iterations;
+ }
+ iterations = Math.min( Math.floor(iterations), LGraphTextureBlur.max_iterations );
+ if(iterations == 0) //skip blurring
+ {
+ this.setOutputData(0, tex);
+ return;
+ }
+
+ var intensity = this.properties.intensity;
+ if( this.isInputConnected(2) )
+ {
+ intensity = this.getInputData(2);
+ this.properties.intensity = intensity;
+ }
+
+ //blur sometimes needs an aspect correction
+ var aspect = LiteGraph.camera_aspect;
+ if(!aspect && window.gl !== undefined)
+ aspect = gl.canvas.height / gl.canvas.width;
+ if(!aspect)
+ aspect = 1;
+ aspect = this.properties.preserve_aspect ? aspect : 1;
+
+ var scale = this.properties.scale || [1,1];
+ tex.applyBlur( aspect * scale[0], scale[1], intensity, temp );
+ for(var i = 1; i < iterations; ++i)
+ temp.applyBlur( aspect * scale[0] * (i+1), scale[1] * (i+1), intensity );
+
+ this.setOutputData(0, temp );
+ }
+
+ /*
+ LGraphTextureBlur.pixel_shader = "precision highp float;\n\
+ precision highp float;\n\
+ varying vec2 v_coord;\n\
+ uniform sampler2D u_texture;\n\
+ uniform vec2 u_offset;\n\
+ uniform float u_intensity;\n\
+ void main() {\n\
+ vec4 sum = vec4(0.0);\n\
+ vec4 center = texture2D(u_texture, v_coord);\n\
+ sum += texture2D(u_texture, v_coord + u_offset * -4.0) * 0.05/0.98;\n\
+ sum += texture2D(u_texture, v_coord + u_offset * -3.0) * 0.09/0.98;\n\
+ sum += texture2D(u_texture, v_coord + u_offset * -2.0) * 0.12/0.98;\n\
+ sum += texture2D(u_texture, v_coord + u_offset * -1.0) * 0.15/0.98;\n\
+ sum += center * 0.16/0.98;\n\
+ sum += texture2D(u_texture, v_coord + u_offset * 4.0) * 0.05/0.98;\n\
+ sum += texture2D(u_texture, v_coord + u_offset * 3.0) * 0.09/0.98;\n\
+ sum += texture2D(u_texture, v_coord + u_offset * 2.0) * 0.12/0.98;\n\
+ sum += texture2D(u_texture, v_coord + u_offset * 1.0) * 0.15/0.98;\n\
+ gl_FragColor = u_intensity * sum;\n\
+ }\n\
+ ";
+ */
+
+ LiteGraph.registerNodeType("texture/blur", LGraphTextureBlur );
+
+
+ // Texture Glow *****************************************
+ //based in https://catlikecoding.com/unity/tutorials/advanced-rendering/bloom/
+ function LGraphTextureGlow()
+ {
+ this.addInput("in","Texture");
+ this.addInput("dirt","Texture");
+ this.addOutput("out","Texture");
+ this.addOutput("glow","Texture");
+ this.properties = { enabled: true, intensity: 1, persistence: 0.99, iterations:16, threshold:0, scale: 1, dirt_factor: 0.5, precision: LGraphTexture.DEFAULT };
+ this._textures = [];
+ this._uniforms = { u_intensity: 1, u_texture: 0, u_glow_texture: 1, u_threshold: 0, u_texel_size: vec2.create() };
+ }
+
+ LGraphTextureGlow.title = "Glow";
+ LGraphTextureGlow.desc = "Filters a texture giving it a glow effect";
+ LGraphTextureGlow.weights = new Float32Array( [0.5,0.4,0.3,0.2] );
+
+ LGraphTextureGlow.widgets_info = {
+ "iterations": { type:"number", min: 0, max: 16, step: 1, precision: 0 },
+ "threshold": { type:"number", min: 0, max: 10, step: 0.01, precision: 2 },
+ "precision": { widget:"combo", values: LGraphTexture.MODE_VALUES }
+ };
+
+ LGraphTextureGlow.prototype.onGetInputs = function(){
+ return [["enabled","boolean"],["threshold","number"],["intensity","number"],["persistence","number"],["iterations","number"],["dirt_factor","number"]];
+ }
+
+ LGraphTextureGlow.prototype.onGetOutputs = function(){
+ return [["average","Texture"]];
+ }
+
+ LGraphTextureGlow.prototype.onExecute = function()
+ {
+ var tex = this.getInputData(0);
+ if(!tex)
+ return;
+
+ if(!this.isAnyOutputConnected())
+ return; //saves work
+
+ if(this.properties.precision === LGraphTexture.PASS_THROUGH || this.getInputOrProperty("enabled" ) === false )
+ {
+ this.setOutputData(0,tex);
+ return;
+ }
+
+ var width = tex.width;
+ var height = tex.height;
+
+ var texture_info = { format: tex.format, type: tex.type, minFilter: GL.LINEAR, magFilter: GL.LINEAR, wrap: gl.CLAMP_TO_EDGE };
+ var type = LGraphTexture.getTextureType( this.properties.precision, tex );
+
+ var uniforms = this._uniforms;
+ var textures = this._textures;
+
+ //cut
+ var shader = LGraphTextureGlow._cut_shader;
+ if(!shader)
+ shader = LGraphTextureGlow._cut_shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureGlow.cut_pixel_shader );
+
+ gl.disable( gl.DEPTH_TEST );
+ gl.disable( gl.BLEND );
+
+ uniforms.u_threshold = this.getInputOrProperty("threshold");
+ var currentDestination = textures[0] = GL.Texture.getTemporary( width, height, texture_info );
+ tex.blit( currentDestination, shader.uniforms(uniforms) );
+ var currentSource = currentDestination;
+
+ var iterations = this.getInputOrProperty("iterations");
+ iterations = Math.clamp( iterations, 1, 16) | 0;
+ var texel_size = uniforms.u_texel_size;
+ var intensity = this.getInputOrProperty("intensity");
+
+ uniforms.u_intensity = 1;
+ uniforms.u_delta = this.properties.scale; //1
+
+ //downscale/upscale shader
+ var shader = LGraphTextureGlow._shader;
+ if(!shader)
+ shader = LGraphTextureGlow._shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureGlow.scale_pixel_shader );
+
+ var i = 1;
+ //downscale
+ for (;i < iterations; i++) {
+ width = width>>1;
+ if( (height|0) > 1 )
+ height = height>>1;
+ if( width < 2 )
+ break;
+ currentDestination = textures[i] = GL.Texture.getTemporary( width, height, texture_info );
+ texel_size[0] = 1 / currentSource.width; texel_size[1] = 1 / currentSource.height;
+ currentSource.blit( currentDestination, shader.uniforms(uniforms) );
+ currentSource = currentDestination;
+ }
+
+ //average
+ if(this.isOutputConnected(2))
+ {
+ var average_texture = this._average_texture;
+ if(!average_texture || average_texture.type != tex.type || average_texture.format != tex.format )
+ average_texture = this._average_texture = new GL.Texture( 1, 1, { type: tex.type, format: tex.format, filter: gl.LINEAR });
+ texel_size[0] = 1 / currentSource.width; texel_size[1] = 1 / currentSource.height;
+ uniforms.u_intensity = intensity;
+ uniforms.u_delta = 1;
+ currentSource.blit( average_texture, shader.uniforms(uniforms) );
+ this.setOutputData( 2, average_texture );
+ }
+
+ //upscale and blend
+ gl.enable( gl.BLEND );
+ gl.blendFunc( gl.ONE, gl.ONE );
+ uniforms.u_intensity = this.getInputOrProperty("persistence");
+ uniforms.u_delta = 0.5;
+
+ for (i -= 2; i >= 0; i--) // i-=2 => -1 to point to last element in array, -1 to go to texture above
+ {
+ currentDestination = textures[i];
+ textures[i] = null;
+ texel_size[0] = 1 / currentSource.width; texel_size[1] = 1 / currentSource.height;
+ currentSource.blit( currentDestination, shader.uniforms(uniforms) );
+ GL.Texture.releaseTemporary( currentSource );
+ currentSource = currentDestination;
+ }
+ gl.disable( gl.BLEND );
+
+ //glow
+ if(this.isOutputConnected(1))
+ {
+ var glow_texture = this._glow_texture;
+ if(!glow_texture || glow_texture.width != tex.width || glow_texture.height != tex.height || glow_texture.type != type || glow_texture.format != tex.format )
+ glow_texture = this._glow_texture = new GL.Texture( tex.width, tex.height, { type: type, format: tex.format, filter: gl.LINEAR });
+ currentSource.blit( glow_texture );
+ this.setOutputData( 1, glow_texture);
+ }
+
+ //final composition
+ if(this.isOutputConnected(0))
+ {
+ var final_texture = this._final_texture;
+ if(!final_texture || final_texture.width != tex.width || final_texture.height != tex.height || final_texture.type != type || final_texture.format != tex.format )
+ final_texture = this._final_texture = new GL.Texture( tex.width, tex.height, { type: type, format: tex.format, filter: gl.LINEAR });
+
+ var dirt_texture = this.getInputData(1);
+ var dirt_factor = this.getInputOrProperty("dirt_factor");
+
+ uniforms.u_intensity = intensity;
+
+ shader = dirt_texture ? LGraphTextureGlow._dirt_final_shader : LGraphTextureGlow._final_shader;
+ if(!shader)
+ {
+ if(dirt_texture)
+ shader = LGraphTextureGlow._dirt_final_shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureGlow.final_pixel_shader, { USE_DIRT: "" } );
+ else
+ shader = LGraphTextureGlow._final_shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureGlow.final_pixel_shader );
+ }
+
+ final_texture.drawTo( function(){
+ tex.bind(0);
+ currentSource.bind(1);
+ if(dirt_texture)
+ {
+ shader.setUniform( "u_dirt_factor", dirt_factor );
+ shader.setUniform( "u_dirt_texture", dirt_texture.bind(2) );
+ }
+ shader.toViewport( uniforms );
+ });
+ this.setOutputData( 0, final_texture );
+ }
+
+ GL.Texture.releaseTemporary( currentSource );
+ }
+
+ LGraphTextureGlow.cut_pixel_shader = "precision highp float;\n\
+ varying vec2 v_coord;\n\
+ uniform sampler2D u_texture;\n\
+ uniform float u_threshold;\n\
+ void main() {\n\
+ gl_FragColor = max( texture2D( u_texture, v_coord ) - vec4( u_threshold ), vec4(0.0) );\n\
+ }"
+
+ LGraphTextureGlow.scale_pixel_shader = "precision highp float;\n\
+ varying vec2 v_coord;\n\
+ uniform sampler2D u_texture;\n\
+ uniform vec2 u_texel_size;\n\
+ uniform float u_delta;\n\
+ uniform float u_intensity;\n\
+ \n\
+ vec4 sampleBox(vec2 uv) {\n\
+ vec4 o = u_texel_size.xyxy * vec2(-u_delta, u_delta).xxyy;\n\
+ vec4 s = texture2D( u_texture, uv + o.xy ) + texture2D( u_texture, uv + o.zy) + texture2D( u_texture, uv + o.xw) + texture2D( u_texture, uv + o.zw);\n\
+ return s * 0.25;\n\
+ }\n\
+ void main() {\n\
+ gl_FragColor = u_intensity * sampleBox( v_coord );\n\
+ }"
+
+ LGraphTextureGlow.final_pixel_shader = "precision highp float;\n\
+ varying vec2 v_coord;\n\
+ uniform sampler2D u_texture;\n\
+ uniform sampler2D u_glow_texture;\n\
+ #ifdef USE_DIRT\n\
+ uniform sampler2D u_dirt_texture;\n\
+ #endif\n\
+ uniform vec2 u_texel_size;\n\
+ uniform float u_delta;\n\
+ uniform float u_intensity;\n\
+ uniform float u_dirt_factor;\n\
+ \n\
+ vec4 sampleBox(vec2 uv) {\n\
+ vec4 o = u_texel_size.xyxy * vec2(-u_delta, u_delta).xxyy;\n\
+ vec4 s = texture2D( u_glow_texture, uv + o.xy ) + texture2D( u_glow_texture, uv + o.zy) + texture2D( u_glow_texture, uv + o.xw) + texture2D( u_glow_texture, uv + o.zw);\n\
+ return s * 0.25;\n\
+ }\n\
+ void main() {\n\
+ vec4 glow = sampleBox( v_coord );\n\
+ #ifdef USE_DIRT\n\
+ glow = mix( glow, glow * texture2D( u_dirt_texture, v_coord ), u_dirt_factor );\n\
+ #endif\n\
+ gl_FragColor = texture2D( u_texture, v_coord ) + u_intensity * glow;\n\
+ }"
+
+ LiteGraph.registerNodeType("texture/glow", LGraphTextureGlow );
+
+
+ // Texture Blur *****************************************
+ function LGraphTextureKuwaharaFilter()
+ {
+ this.addInput("Texture","Texture");
+ this.addOutput("Filtered","Texture");
+ this.properties = { intensity: 1, radius: 5 };
+ }
+
+ LGraphTextureKuwaharaFilter.title = "Kuwahara Filter";
+ LGraphTextureKuwaharaFilter.desc = "Filters a texture giving an artistic oil canvas painting";
+
+ LGraphTextureKuwaharaFilter.max_radius = 10;
+ LGraphTextureKuwaharaFilter._shaders = [];
+
+ LGraphTextureKuwaharaFilter.prototype.onExecute = function()
+ {
+ var tex = this.getInputData(0);
+ if(!tex)
+ return;
+
+ if(!this.isOutputConnected(0))
+ return; //saves work
+
+ var temp = this._temp_texture;
+
+ if(!temp || temp.width != tex.width || temp.height != tex.height || temp.type != tex.type )
+ {
+ //we need two textures to do the blurring
+ this._temp_texture = new GL.Texture( tex.width, tex.height, { type: tex.type, format: gl.RGBA, filter: gl.LINEAR });
+ //this._final_texture = new GL.Texture( tex.width, tex.height, { type: tex.type, format: gl.RGBA, filter: gl.LINEAR });
+ }
+
+ //iterations
+ var radius = this.properties.radius;
+ radius = Math.min( Math.floor(radius), LGraphTextureKuwaharaFilter.max_radius );
+ if(radius == 0) //skip blurring
+ {
+ this.setOutputData(0, tex);
+ return;
+ }
+
+ var intensity = this.properties.intensity;
+
+ //blur sometimes needs an aspect correction
+ var aspect = LiteGraph.camera_aspect;
+ if(!aspect && window.gl !== undefined)
+ aspect = gl.canvas.height / gl.canvas.width;
+ if(!aspect)
+ aspect = 1;
+ aspect = this.properties.preserve_aspect ? aspect : 1;
+
+ if(!LGraphTextureKuwaharaFilter._shaders[ radius ])
+ LGraphTextureKuwaharaFilter._shaders[ radius ] = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphTextureKuwaharaFilter.pixel_shader, { RADIUS: radius.toFixed(0) });
+
+ var shader = LGraphTextureKuwaharaFilter._shaders[ radius ];
+ var mesh = GL.Mesh.getScreenQuad();
+ tex.bind(0);
+
+ this._temp_texture.drawTo( function() {
+ shader.uniforms({ u_texture: 0, u_intensity: intensity, u_resolution: [tex.width, tex.height], u_iResolution: [1/tex.width,1/tex.height]}).draw(mesh);
+ });
+
+ this.setOutputData(0, this._temp_texture);
+ }
+
+//from https://www.shadertoy.com/view/MsXSz4
+LGraphTextureKuwaharaFilter.pixel_shader = "\n\
+ precision highp float;\n\
+ varying vec2 v_coord;\n\
+ uniform sampler2D u_texture;\n\
+ uniform float u_intensity;\n\
+ uniform vec2 u_resolution;\n\
+ uniform vec2 u_iResolution;\n\
+ #ifndef RADIUS\n\
+ #define RADIUS 7\n\
+ #endif\n\
+ void main() {\n\
+ \n\
+ const int radius = RADIUS;\n\
+ vec2 fragCoord = v_coord;\n\
+ vec2 src_size = u_iResolution;\n\
+ vec2 uv = v_coord;\n\
+ float n = float((radius + 1) * (radius + 1));\n\
+ int i;\n\
+ int j;\n\
+ vec3 m0 = vec3(0.0); vec3 m1 = vec3(0.0); vec3 m2 = vec3(0.0); vec3 m3 = vec3(0.0);\n\
+ vec3 s0 = vec3(0.0); vec3 s1 = vec3(0.0); vec3 s2 = vec3(0.0); vec3 s3 = vec3(0.0);\n\
+ vec3 c;\n\
+ \n\
+ for (int j = -radius; j <= 0; ++j) {\n\
+ for (int i = -radius; i <= 0; ++i) {\n\
+ c = texture2D(u_texture, uv + vec2(i,j) * src_size).rgb;\n\
+ m0 += c;\n\
+ s0 += c * c;\n\
+ }\n\
+ }\n\
+ \n\
+ for (int j = -radius; j <= 0; ++j) {\n\
+ for (int i = 0; i <= radius; ++i) {\n\
+ c = texture2D(u_texture, uv + vec2(i,j) * src_size).rgb;\n\
+ m1 += c;\n\
+ s1 += c * c;\n\
+ }\n\
+ }\n\
+ \n\
+ for (int j = 0; j <= radius; ++j) {\n\
+ for (int i = 0; i <= radius; ++i) {\n\
+ c = texture2D(u_texture, uv + vec2(i,j) * src_size).rgb;\n\
+ m2 += c;\n\
+ s2 += c * c;\n\
+ }\n\
+ }\n\
+ \n\
+ for (int j = 0; j <= radius; ++j) {\n\
+ for (int i = -radius; i <= 0; ++i) {\n\
+ c = texture2D(u_texture, uv + vec2(i,j) * src_size).rgb;\n\
+ m3 += c;\n\
+ s3 += c * c;\n\
+ }\n\
+ }\n\
+ \n\
+ float min_sigma2 = 1e+2;\n\
+ m0 /= n;\n\
+ s0 = abs(s0 / n - m0 * m0);\n\
+ \n\
+ float sigma2 = s0.r + s0.g + s0.b;\n\
+ if (sigma2 < min_sigma2) {\n\
+ min_sigma2 = sigma2;\n\
+ gl_FragColor = vec4(m0, 1.0);\n\
+ }\n\
+ \n\
+ m1 /= n;\n\
+ s1 = abs(s1 / n - m1 * m1);\n\
+ \n\
+ sigma2 = s1.r + s1.g + s1.b;\n\
+ if (sigma2 < min_sigma2) {\n\
+ min_sigma2 = sigma2;\n\
+ gl_FragColor = vec4(m1, 1.0);\n\
+ }\n\
+ \n\
+ m2 /= n;\n\
+ s2 = abs(s2 / n - m2 * m2);\n\
+ \n\
+ sigma2 = s2.r + s2.g + s2.b;\n\
+ if (sigma2 < min_sigma2) {\n\
+ min_sigma2 = sigma2;\n\
+ gl_FragColor = vec4(m2, 1.0);\n\
+ }\n\
+ \n\
+ m3 /= n;\n\
+ s3 = abs(s3 / n - m3 * m3);\n\
+ \n\
+ sigma2 = s3.r + s3.g + s3.b;\n\
+ if (sigma2 < min_sigma2) {\n\
+ min_sigma2 = sigma2;\n\
+ gl_FragColor = vec4(m3, 1.0);\n\
+ }\n\
+ }\n\
+ ";
+
+ LiteGraph.registerNodeType("texture/kuwahara", LGraphTextureKuwaharaFilter );
+
+
+ // Texture Webcam *****************************************
+ function LGraphTextureWebcam()
+ {
+ this.addOutput("Webcam","Texture");
+ this.properties = { texture_name: "" };
+ }
+
+ LGraphTextureWebcam.title = "Webcam";
+ LGraphTextureWebcam.desc = "Webcam texture";
+
+
+ LGraphTextureWebcam.prototype.openStream = function()
+ {
+ //Vendor prefixes hell
+ navigator.getUserMedia = (navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia);
+ window.URL = window.URL || window.webkitURL;
+
+ if (!navigator.getUserMedia) {
+ //console.log('getUserMedia() is not supported in your browser, use chrome and enable WebRTC from about://flags');
+ return;
+ }
+
+ this._waiting_confirmation = true;
+ var that = this;
+
+ // Not showing vendor prefixes.
+ navigator.getUserMedia({video: true}, this.streamReady.bind(this), onFailSoHard);
+
+ function onFailSoHard(e) {
+ console.log('Webcam rejected', e);
+ that._webcam_stream = false;
+ that.box_color = "red";
+ };
+ }
+
+ LGraphTextureWebcam.prototype.streamReady = function(localMediaStream)
+ {
+ this._webcam_stream = localMediaStream;
+ //this._waiting_confirmation = false;
+
+ var video = this._video;
+ if(!video)
+ {
+ video = document.createElement("video");
+ video.autoplay = true;
+ video.src = window.URL.createObjectURL( localMediaStream );
+ this._video = video;
+ //document.body.appendChild( video ); //debug
+ //when video info is loaded (size and so)
+ video.onloadedmetadata = function(e) {
+ // Ready to go. Do some stuff.
+ console.log(e);
+ };
+ }
+ }
+
+ LGraphTextureWebcam.prototype.onRemoved = function()
+ {
+ if(!this._webcam_stream)
+ return;
+
+ var video_streams = this._webcam_stream.getVideoTracks();
+ if(video_streams.length)
+ {
+ var webcam = video_streams[0];
+ if( webcam.stop )
+ webcam.stop();
+ }
+
+ this._webcam_stream = null;
+ this._video = null;
+ }
+
+ LGraphTextureWebcam.prototype.onDrawBackground = function(ctx)
+ {
+ if(this.flags.collapsed || this.size[1] <= 20)
+ return;
+
+ if(!this._video)
+ return;
+
+ //render to graph canvas
+ ctx.save();
+ if(!ctx.webgl) //reverse image
+ {
+ ctx.translate(0,this.size[1]);
+ ctx.scale(1,-1);
+ ctx.drawImage(this._video, 0, 0, this.size[0], this.size[1]);
+ }
+ else
+ {
+ if(this._temp_texture)
+ ctx.drawImage(this._temp_texture, 0, 0, this.size[0], this.size[1]);
+ }
+ ctx.restore();
+ }
+
+ LGraphTextureWebcam.prototype.onExecute = function()
+ {
+ if(this._webcam_stream == null && !this._waiting_confirmation)
+ this.openStream();
+
+ if(!this._video || !this._video.videoWidth)
+ return;
+
+ var width = this._video.videoWidth;
+ var height = this._video.videoHeight;
+
+ var temp = this._temp_texture;
+ if(!temp || temp.width != width || temp.height != height )
+ this._temp_texture = new GL.Texture( width, height, { format: gl.RGB, filter: gl.LINEAR });
+
+ this._temp_texture.uploadImage( this._video );
+
+ if(this.properties.texture_name)
+ {
+ var container = LGraphTexture.getTexturesContainer();
+ container[ this.properties.texture_name ] = this._temp_texture;
+ }
+
+ this.setOutputData(0,this._temp_texture);
+ }
+
+ LiteGraph.registerNodeType("texture/webcam", LGraphTextureWebcam );
+
+
+
+ //from https://github.com/spite/Wagner
+ function LGraphLensFX()
+ {
+ this.addInput("in","Texture");
+ this.addInput("f","number");
+ this.addOutput("out","Texture");
+ this.properties = { enabled: true, factor: 1, precision: LGraphTexture.LOW };
+
+ this._uniforms = { u_texture: 0, u_factor: 1 };
+ }
+
+ LGraphLensFX.title = "Lens FX";
+ LGraphLensFX.desc = "distortion and chromatic aberration";
+
+ LGraphLensFX.widgets_info = {
+ "precision": { widget:"combo", values: LGraphTexture.MODE_VALUES }
+ };
+
+ LGraphLensFX.prototype.onGetInputs = function() { return [["enabled","boolean"]]; }
+
+ LGraphLensFX.prototype.onExecute = function()
+ {
+ var tex = this.getInputData(0);
+ if(!tex)
+ return;
+
+ if(!this.isOutputConnected(0))
+ return; //saves work
+
+ if(this.properties.precision === LGraphTexture.PASS_THROUGH || this.getInputOrProperty("enabled" ) === false )
+ {
+ this.setOutputData(0, tex );
+ return;
+ }
+
+ var temp = this._temp_texture;
+ if(!temp || temp.width != tex.width || temp.height != tex.height || temp.type != tex.type )
+ temp = this._temp_texture = new GL.Texture( tex.width, tex.height, { type: tex.type, format: gl.RGBA, filter: gl.LINEAR });
+
+ var shader = LGraphLensFX._shader;
+ if(!shader)
+ shader = LGraphLensFX._shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphLensFX.pixel_shader );
+
+ var factor = this.getInputData(1);
+ if(factor == null)
+ factor = this.properties.factor;
+
+ var uniforms = this._uniforms;
+ uniforms.u_factor = factor;
+
+ //apply shader
+ gl.disable( gl.DEPTH_TEST );
+ temp.drawTo(function(){
+ tex.bind(0);
+ shader.uniforms(uniforms).draw( GL.Mesh.getScreenQuad() );
+ });
+
+ this.setOutputData(0,temp);
+ }
+
+ LGraphLensFX.pixel_shader = "precision highp float;\n\
+ varying vec2 v_coord;\n\
+ uniform sampler2D u_texture;\n\
+ uniform float u_factor;\n\
+ vec2 barrelDistortion(vec2 coord, float amt) {\n\
+ vec2 cc = coord - 0.5;\n\
+ float dist = dot(cc, cc);\n\
+ return coord + cc * dist * amt;\n\
+ }\n\
+ \n\
+ float sat( float t )\n\
+ {\n\
+ return clamp( t, 0.0, 1.0 );\n\
+ }\n\
+ \n\
+ float linterp( float t ) {\n\
+ return sat( 1.0 - abs( 2.0*t - 1.0 ) );\n\
+ }\n\
+ \n\
+ float remap( float t, float a, float b ) {\n\
+ return sat( (t - a) / (b - a) );\n\
+ }\n\
+ \n\
+ vec4 spectrum_offset( float t ) {\n\
+ vec4 ret;\n\
+ float lo = step(t,0.5);\n\
+ float hi = 1.0-lo;\n\
+ float w = linterp( remap( t, 1.0/6.0, 5.0/6.0 ) );\n\
+ ret = vec4(lo,1.0,hi, 1.) * vec4(1.0-w, w, 1.0-w, 1.);\n\
+ \n\
+ return pow( ret, vec4(1.0/2.2) );\n\
+ }\n\
+ \n\
+ const float max_distort = 2.2;\n\
+ const int num_iter = 12;\n\
+ const float reci_num_iter_f = 1.0 / float(num_iter);\n\
+ \n\
+ void main()\n\
+ { \n\
+ vec2 uv=v_coord;\n\
+ vec4 sumcol = vec4(0.0);\n\
+ vec4 sumw = vec4(0.0); \n\
+ for ( int i=0; i= 0xF0)
- this.cmd = midiStatus;
- else
- this.cmd = midiCommand;
-
- if(this.cmd == MIDIEvent.NOTEON && this.velocity == 0)
- this.cmd = MIDIEvent.NOTEOFF;
-
- this.cmd_str = MIDIEvent.commands[ this.cmd ] || "";
-
- if ( midiCommand >= MIDIEvent.NOTEON || midiCommand <= MIDIEvent.NOTEOFF ) {
- this.channel = midiStatus & 0x0F;
- }
-}
-
-Object.defineProperty( MIDIEvent.prototype, "velocity", {
- get: function() {
- if(this.cmd == MIDIEvent.NOTEON)
- return this.data[2];
- return -1;
- },
- set: function(v) {
- this.data[2] = v; // v / 127;
- },
- enumerable: true
-});
-
-MIDIEvent.notes = ["A","A#","B","C","C#","D","D#","E","F","F#","G","G#"];
-
-//returns HZs
-MIDIEvent.prototype.getPitch = function()
-{
- return Math.pow(2, (this.data[1] - 69) / 12 ) * 440;
-}
-
-MIDIEvent.computePitch = function( note )
-{
- return Math.pow(2, (note - 69) / 12 ) * 440;
-}
-
-MIDIEvent.prototype.getCC = function()
-{
- return this.data[1];
-}
-
-MIDIEvent.prototype.getCCValue = function()
-{
- return this.data[2];
-}
-
-//not tested, there is a formula missing here
-MIDIEvent.prototype.getPitchBend = function()
-{
- return this.data[1] + (this.data[2] << 7) - 8192;
-}
-
-MIDIEvent.computePitchBend = function(v1,v2)
-{
- return v1 + (v2 << 7) - 8192;
-}
-
-MIDIEvent.prototype.setCommandFromString = function( str )
-{
- this.cmd = MIDIEvent.computeCommandFromString(str);
-}
-
-MIDIEvent.computeCommandFromString = function( str )
-{
- if(!str)
- return 0;
-
- if(str && str.constructor === Number)
- return str;
-
- str = str.toUpperCase();
- switch( str )
- {
- case "NOTE ON":
- case "NOTEON": return MIDIEvent.NOTEON; break;
- case "NOTE OFF":
- case "NOTEOFF": return MIDIEvent.NOTEON; break;
- case "KEY PRESSURE":
- case "KEYPRESSURE": return MIDIEvent.KEYPRESSURE; break;
- case "CONTROLLER CHANGE":
- case "CONTROLLERCHANGE":
- case "CC": return MIDIEvent.CONTROLLERCHANGE; break;
- case "PROGRAM CHANGE":
- case "PROGRAMCHANGE":
- case "PC": return MIDIEvent.PROGRAMCHANGE; break;
- case "CHANNEL PRESSURE":
- case "CHANNELPRESSURE": return MIDIEvent.CHANNELPRESSURE; break;
- case "PITCH BEND":
- case "PITCHBEND": return MIDIEvent.PITCHBEND; break;
- case "TIME TICK":
- case "TIMETICK": return MIDIEvent.TIMETICK; break;
- default: return Number(str); //asume its a hex code
- }
-}
-
-MIDIEvent.toNoteString = function(d)
-{
- var note = d - 21;
- var octave = d - 24;
- note = note % 12;
- if(note < 0)
- note = 12 + note;
- return MIDIEvent.notes[ note ] + Math.floor(octave / 12 + 1);
-}
-
-MIDIEvent.prototype.toString = function()
-{
- var str = "" + this.channel + ". " ;
- switch( this.cmd )
- {
- case MIDIEvent.NOTEON: str += "NOTEON " + MIDIEvent.toNoteString( this.data[1] ); break;
- case MIDIEvent.NOTEOFF: str += "NOTEOFF " + MIDIEvent.toNoteString( this.data[1] ); break;
- case MIDIEvent.CONTROLLERCHANGE: str += "CC " + this.data[1] + " " + this.data[2]; break;
- case MIDIEvent.PROGRAMCHANGE: str += "PC " + this.data[1]; break;
- case MIDIEvent.PITCHBEND: str += "PITCHBEND " + this.getPitchBend(); break;
- case MIDIEvent.KEYPRESSURE: str += "KEYPRESS " + this.data[1]; break;
- }
-
- return str;
-}
-
-MIDIEvent.prototype.toHexString = function()
-{
- var str = "";
- for(var i = 0; i < this.data.length; i++)
- str += this.data[i].toString(16) + " ";
-}
-
-MIDIEvent.NOTEOFF = 0x80;
-MIDIEvent.NOTEON = 0x90;
-MIDIEvent.KEYPRESSURE = 0xA0;
-MIDIEvent.CONTROLLERCHANGE = 0xB0;
-MIDIEvent.PROGRAMCHANGE = 0xC0;
-MIDIEvent.CHANNELPRESSURE = 0xD0;
-MIDIEvent.PITCHBEND = 0xE0;
-MIDIEvent.TIMETICK = 0xF8;
-
-MIDIEvent.commands = {
- 0x80: "note off",
- 0x90: "note on",
- 0xA0: "key pressure",
- 0xB0: "controller change",
- 0xC0: "program change",
- 0xD0: "channel pressure",
- 0xE0: "pitch bend",
- 0xF0: "system",
- 0xF2: "Song pos",
- 0xF3: "Song select",
- 0xF6: "Tune request",
- 0xF8: "time tick",
- 0xFA: "Start Song",
- 0xFB: "Continue Song",
- 0xFC: "Stop Song",
- 0xFE: "Sensing",
- 0xFF: "Reset"
-}
-
-//MIDI wrapper
-function MIDIInterface( on_ready, on_error )
-{
- if(!navigator.requestMIDIAccess)
- {
- this.error = "not suppoorted";
- if(on_error)
- on_error("Not supported");
- else
- console.error("MIDI NOT SUPPORTED, enable by chrome://flags");
- return;
- }
-
- this.on_ready = on_ready;
-
- this.state = {
- note: [],
- cc: []
- };
-
-
-
- navigator.requestMIDIAccess().then( this.onMIDISuccess.bind(this), this.onMIDIFailure.bind(this) );
-}
-
-MIDIInterface.input = null;
-
-MIDIInterface.MIDIEvent = MIDIEvent;
-
-MIDIInterface.prototype.onMIDISuccess = function(midiAccess)
-{
- console.log( "MIDI ready!" );
- console.log( midiAccess );
- this.midi = midiAccess; // store in the global (in real usage, would probably keep in an object instance)
- this.updatePorts();
-
- if (this.on_ready)
- this.on_ready(this);
-}
-
-MIDIInterface.prototype.updatePorts = function()
-{
- var midi = this.midi;
- this.input_ports = midi.inputs;
- var num = 0;
-
- var it = this.input_ports.values();
- var it_value = it.next();
- while( it_value && it_value.done === false )
- {
- var port_info = it_value.value;
- console.log( "Input port [type:'" + port_info.type + "'] id:'" + port_info.id +
- "' manufacturer:'" + port_info.manufacturer + "' name:'" + port_info.name +
- "' version:'" + port_info.version + "'" );
- num++;
- it_value = it.next();
- }
- this.num_input_ports = num;
-
- num = 0;
- this.output_ports = midi.outputs;
- var it = this.output_ports.values();
- var it_value = it.next();
- while( it_value && it_value.done === false )
- {
- var port_info = it_value.value;
- console.log( "Output port [type:'" + port_info.type + "'] id:'" + port_info.id +
- "' manufacturer:'" + port_info.manufacturer + "' name:'" + port_info.name +
- "' version:'" + port_info.version + "'" );
- num++;
- it_value = it.next();
- }
- this.num_output_ports = num;
-
-
- /* OLD WAY
- for (var i = 0; i < this.input_ports.size; ++i) {
- var input = this.input_ports.get(i);
- if(!input)
- continue; //sometimes it is null?!
- console.log( "Input port [type:'" + input.type + "'] id:'" + input.id +
- "' manufacturer:'" + input.manufacturer + "' name:'" + input.name +
- "' version:'" + input.version + "'" );
- num++;
- }
- this.num_input_ports = num;
-
-
- num = 0;
- this.output_ports = midi.outputs;
- for (var i = 0; i < this.output_ports.size; ++i) {
- var output = this.output_ports.get(i);
- if(!output)
- continue;
- console.log( "Output port [type:'" + output.type + "'] id:'" + output.id +
- "' manufacturer:'" + output.manufacturer + "' name:'" + output.name +
- "' version:'" + output.version + "'" );
- num++;
- }
- this.num_output_ports = num;
- */
-}
-
-MIDIInterface.prototype.onMIDIFailure = function(msg)
-{
- console.error( "Failed to get MIDI access - " + msg );
-}
-
-MIDIInterface.prototype.openInputPort = function( port, callback )
-{
- var input_port = this.input_ports.get( "input-" + port );
- if(!input_port)
- return false;
- MIDIInterface.input = this;
- var that = this;
-
- input_port.onmidimessage = function(a) {
- var midi_event = new MIDIEvent(a.data);
- that.updateState( midi_event );
- if(callback)
- callback(a.data, midi_event );
- if(MIDIInterface.on_message)
- MIDIInterface.on_message( a.data, midi_event );
- }
- console.log("port open: ", input_port);
- return true;
-}
-
-MIDIInterface.parseMsg = function(data)
-{
-
-}
-
-MIDIInterface.prototype.updateState = function( midi_event )
-{
- switch( midi_event.cmd )
- {
- case MIDIEvent.NOTEON: this.state.note[ midi_event.value1|0 ] = midi_event.value2; break;
- case MIDIEvent.NOTEOFF: this.state.note[ midi_event.value1|0 ] = 0; break;
- case MIDIEvent.CONTROLLERCHANGE: this.state.cc[ midi_event.getCC() ] = midi_event.getCCValue(); break;
- }
-}
-
-MIDIInterface.prototype.sendMIDI = function( port, midi_data )
-{
- if( !midi_data )
- return;
-
- var output_port = this.output_ports.get( "output-" + port );
- if(!output_port)
- return;
-
- MIDIInterface.output = this;
-
- if( midi_data.constructor === MIDIEvent)
- output_port.send( midi_data.data );
- else
- output_port.send( midi_data );
-}
-
-
-
-function LGMIDIIn()
-{
- this.addOutput( "on_midi", LiteGraph.EVENT );
- this.addOutput( "out", "midi" );
- this.properties = {port: 0};
- this._last_midi_event = null;
- this._current_midi_event = null;
-
- var that = this;
- new MIDIInterface( function( midi ){
- //open
- that._midi = midi;
- if(that._waiting)
- that.onStart();
- that._waiting = false;
- });
-}
-
-LGMIDIIn.MIDIInterface = MIDIInterface;
-
-LGMIDIIn.title = "MIDI Input";
-LGMIDIIn.desc = "Reads MIDI from a input port";
-
-LGMIDIIn.prototype.getPropertyInfo = function(name)
-{
- if(!this._midi)
- return;
-
- if(name == "port")
- {
- var values = {};
- for (var i = 0; i < this._midi.input_ports.size; ++i)
- {
- var input = this._midi.input_ports.get( "input-" + i);
- values[i] = i + ".- " + input.name + " version:" + input.version;
- }
- return { type: "enum", values: values };
- }
-}
-
-LGMIDIIn.prototype.onStart = function()
-{
- if(this._midi)
- this._midi.openInputPort( this.properties.port, this.onMIDIEvent.bind(this) );
- else
- this._waiting = true;
-}
-
-LGMIDIIn.prototype.onMIDIEvent = function( data, midi_event )
-{
- this._last_midi_event = midi_event;
-
- this.trigger( "on_midi", midi_event );
- if(midi_event.cmd == MIDIEvent.NOTEON)
- this.trigger( "on_noteon", midi_event );
- else if(midi_event.cmd == MIDIEvent.NOTEOFF)
- this.trigger( "on_noteoff", midi_event );
- else if(midi_event.cmd == MIDIEvent.CONTROLLERCHANGE)
- this.trigger( "on_cc", midi_event );
- else if(midi_event.cmd == MIDIEvent.PROGRAMCHANGE)
- this.trigger( "on_pc", midi_event );
- else if(midi_event.cmd == MIDIEvent.PITCHBEND)
- this.trigger( "on_pitchbend", midi_event );
-}
-
-LGMIDIIn.prototype.onExecute = function()
-{
- if(this.outputs)
- {
- var last = this._last_midi_event;
- for(var i = 0; i < this.outputs.length; ++i)
- {
- var output = this.outputs[i];
- var v = null;
- switch (output.name)
- {
- case "midi": v = this._midi; break;
- case "last_midi": v = last; break;
- default:
- continue;
- }
- this.setOutputData( i, v );
- }
- }
-}
-
-LGMIDIIn.prototype.onGetOutputs = function() {
- return [
- ["last_midi","midi"],
- ["on_midi",LiteGraph.EVENT],
- ["on_noteon",LiteGraph.EVENT],
- ["on_noteoff",LiteGraph.EVENT],
- ["on_cc",LiteGraph.EVENT],
- ["on_pc",LiteGraph.EVENT],
- ["on_pitchbend",LiteGraph.EVENT]
- ];
-}
-
-LiteGraph.registerNodeType("midi/input", LGMIDIIn);
-
-
-function LGMIDIOut()
-{
- this.addInput( "send", LiteGraph.EVENT );
- this.properties = {port: 0};
-
- var that = this;
- new MIDIInterface( function( midi ){
- that._midi = midi;
- });
-}
-
-LGMIDIOut.MIDIInterface = MIDIInterface;
-
-LGMIDIOut.title = "MIDI Output";
-LGMIDIOut.desc = "Sends MIDI to output channel";
-
-LGMIDIOut.prototype.getPropertyInfo = function(name)
-{
- if(!this._midi)
- return;
-
- if(name == "port")
- {
- var values = {};
- for (var i = 0; i < this._midi.output_ports.size; ++i)
- {
- var output = this._midi.output_ports.get(i);
- values[i] = i + ".- " + output.name + " version:" + output.version;
- }
- return { type: "enum", values: values };
- }
-}
-
-
-LGMIDIOut.prototype.onAction = function(event, midi_event )
-{
- console.log(midi_event);
- if(!this._midi)
- return;
- if(event == "send")
- this._midi.sendMIDI( this.port, midi_event );
- this.trigger("midi",midi_event);
-}
-
-LGMIDIOut.prototype.onGetInputs = function() {
- return [["send",LiteGraph.ACTION]];
-}
-
-LGMIDIOut.prototype.onGetOutputs = function() {
- return [["on_midi",LiteGraph.EVENT]];
-}
-
-LiteGraph.registerNodeType("midi/output", LGMIDIOut);
-
-
-function LGMIDIShow()
-{
- this.addInput( "on_midi", LiteGraph.EVENT );
- this._str = "";
- this.size = [200,40]
-}
-
-LGMIDIShow.title = "MIDI Show";
-LGMIDIShow.desc = "Shows MIDI in the graph";
-
-LGMIDIShow.prototype.onAction = function(event, midi_event )
-{
- if(!midi_event)
- return;
- if(midi_event.constructor === MIDIEvent)
- this._str = midi_event.toString();
- else
- this._str = "???";
-}
-
-LGMIDIShow.prototype.onDrawForeground = function( ctx )
-{
- if( !this._str )
- return;
-
- ctx.font = "30px Arial";
- ctx.fillText( this._str, 10, this.size[1] * 0.8 );
-}
-
-LGMIDIShow.prototype.onGetInputs = function() {
- return [["in",LiteGraph.ACTION]];
-}
-
-LGMIDIShow.prototype.onGetOutputs = function() {
- return [["on_midi",LiteGraph.EVENT]];
-}
-
-LiteGraph.registerNodeType("midi/show", LGMIDIShow);
-
-
-
-function LGMIDIFilter()
-{
- this.properties = {
- channel: -1,
- cmd: -1,
- min_value: -1,
- max_value: -1
- };
-
- this.addInput( "in", LiteGraph.EVENT );
- this.addOutput( "on_midi", LiteGraph.EVENT );
-}
-
-LGMIDIFilter.title = "MIDI Filter";
-LGMIDIFilter.desc = "Filters MIDI messages";
-
-LGMIDIFilter.prototype.onAction = function(event, midi_event )
-{
- if(!midi_event || midi_event.constructor !== MIDIEvent)
- return;
-
- if( this.properties.channel != -1 && midi_event.channel != this.properties.channel)
- return;
- if(this.properties.cmd != -1 && midi_event.cmd != this.properties.cmd)
- return;
- if(this.properties.min_value != -1 && midi_event.data[1] < this.properties.min_value)
- return;
- if(this.properties.max_value != -1 && midi_event.data[1] > this.properties.max_value)
- return;
- this.trigger("on_midi",midi_event);
-}
-
-LiteGraph.registerNodeType("midi/filter", LGMIDIFilter);
-
-
-function LGMIDIEvent()
-{
- this.properties = {
- channel: 0,
- cmd: "CC",
- value1: 1,
- value2: 1
- };
-
- this.addInput( "send", LiteGraph.EVENT );
- this.addInput( "assign", LiteGraph.EVENT );
- this.addOutput( "on_midi", LiteGraph.EVENT );
-}
-
-LGMIDIEvent.title = "MIDIEvent";
-LGMIDIEvent.desc = "Create a MIDI Event";
-
-LGMIDIEvent.prototype.onAction = function( event, midi_event )
-{
- if(event == "assign")
- {
- this.properties.channel = midi_event.channel;
- this.properties.cmd = midi_event.cmd;
- this.properties.value1 = midi_event.data[1];
- this.properties.value2 = midi_event.data[2];
- return;
- }
-
- //send
- var midi_event = new MIDIEvent();
- midi_event.channel = this.properties.channel;
- if(this.properties.cmd && this.properties.cmd.constructor === String)
- midi_event.setCommandFromString( this.properties.cmd );
- else
- midi_event.cmd = this.properties.cmd;
- midi_event.data[0] = midi_event.cmd | midi_event.channel;
- midi_event.data[1] = Number(this.properties.value1);
- midi_event.data[2] = Number(this.properties.value2);
- this.trigger("on_midi",midi_event);
-}
-
-LGMIDIEvent.prototype.onExecute = function()
-{
- var props = this.properties;
-
- if(this.outputs)
- {
- for(var i = 0; i < this.outputs.length; ++i)
- {
- var output = this.outputs[i];
- var v = null;
- switch (output.name)
- {
- case "midi":
- v = new MIDIEvent();
- v.setup([ props.cmd, props.value1, props.value2 ]);
- v.channel = props.channel;
- break;
- case "command": v = props.cmd; break;
- case "cc": v = props.value1; break;
- case "cc_value": v = props.value2; break;
- case "note": v = (props.cmd == MIDIEvent.NOTEON || props.cmd == MIDIEvent.NOTEOFF) ? props.value1 : null; break;
- case "velocity": v = props.cmd == MIDIEvent.NOTEON ? props.value2 : null; break;
- case "pitch": v = props.cmd == MIDIEvent.NOTEON ? MIDIEvent.computePitch( props.value1 ) : null; break;
- case "pitchbend": v = props.cmd == MIDIEvent.PITCHBEND ? MIDIEvent.computePitchBend( props.value1, props.value2 ) : null; break;
- default:
- continue;
- }
- if(v !== null)
- this.setOutputData( i, v );
- }
- }
-}
-
-LGMIDIEvent.prototype.onPropertyChanged = function(name,value)
-{
- if(name == "cmd")
- this.properties.cmd = MIDIEvent.computeCommandFromString( value );
-}
-
-
-LGMIDIEvent.prototype.onGetOutputs = function() {
- return [
- ["midi","midi"],
- ["on_midi",LiteGraph.EVENT],
- ["command","number"],
- ["note","number"],
- ["velocity","number"],
- ["cc","number"],
- ["cc_value","number"],
- ["pitch","number"],
- ["pitchbend","number"]
- ];
-}
-
-
-LiteGraph.registerNodeType("midi/event", LGMIDIEvent);
-
-
-function LGMIDICC()
-{
- this.properties = {
-// channel: 0,
- cc: 1,
- value: 0
- };
-
- this.addOutput( "value", "number" );
-}
-
-LGMIDICC.title = "MIDICC";
-LGMIDICC.desc = "gets a Controller Change";
-
-LGMIDICC.prototype.onExecute = function()
-{
- var props = this.properties;
- if( MIDIInterface.input )
- this.properties.value = MIDIInterface.input.state.cc[ this.properties.cc ];
- this.setOutputData( 0, this.properties.value );
-}
-
-LiteGraph.registerNodeType("midi/cc", LGMIDICC);
-
-
-
-
-function now() { return window.performance.now() }
-
+(function( global )
+{
+var LiteGraph = global.LiteGraph;
+
+function MIDIEvent( data )
+{
+ this.channel = 0;
+ this.cmd = 0;
+
+ if(data)
+ this.setup(data)
+ else
+ this.data = [0,0,0];
+}
+
+MIDIEvent.prototype.setup = function( raw_data )
+{
+ this.data = raw_data;
+
+ var midiStatus = raw_data[0];
+ this.status = midiStatus;
+
+ var midiCommand = midiStatus & 0xF0;
+
+ if(midiStatus >= 0xF0)
+ this.cmd = midiStatus;
+ else
+ this.cmd = midiCommand;
+
+ if(this.cmd == MIDIEvent.NOTEON && this.velocity == 0)
+ this.cmd = MIDIEvent.NOTEOFF;
+
+ this.cmd_str = MIDIEvent.commands[ this.cmd ] || "";
+
+ if ( midiCommand >= MIDIEvent.NOTEON || midiCommand <= MIDIEvent.NOTEOFF ) {
+ this.channel = midiStatus & 0x0F;
+ }
+}
+
+Object.defineProperty( MIDIEvent.prototype, "velocity", {
+ get: function() {
+ if(this.cmd == MIDIEvent.NOTEON)
+ return this.data[2];
+ return -1;
+ },
+ set: function(v) {
+ this.data[2] = v; // v / 127;
+ },
+ enumerable: true
+});
+
+MIDIEvent.notes = ["A","A#","B","C","C#","D","D#","E","F","F#","G","G#"];
+
+//returns HZs
+MIDIEvent.prototype.getPitch = function()
+{
+ return Math.pow(2, (this.data[1] - 69) / 12 ) * 440;
+}
+
+MIDIEvent.computePitch = function( note )
+{
+ return Math.pow(2, (note - 69) / 12 ) * 440;
+}
+
+MIDIEvent.prototype.getCC = function()
+{
+ return this.data[1];
+}
+
+MIDIEvent.prototype.getCCValue = function()
+{
+ return this.data[2];
+}
+
+//not tested, there is a formula missing here
+MIDIEvent.prototype.getPitchBend = function()
+{
+ return this.data[1] + (this.data[2] << 7) - 8192;
+}
+
+MIDIEvent.computePitchBend = function(v1,v2)
+{
+ return v1 + (v2 << 7) - 8192;
+}
+
+MIDIEvent.prototype.setCommandFromString = function( str )
+{
+ this.cmd = MIDIEvent.computeCommandFromString(str);
+}
+
+MIDIEvent.computeCommandFromString = function( str )
+{
+ if(!str)
+ return 0;
+
+ if(str && str.constructor === Number)
+ return str;
+
+ str = str.toUpperCase();
+ switch( str )
+ {
+ case "NOTE ON":
+ case "NOTEON": return MIDIEvent.NOTEON; break;
+ case "NOTE OFF":
+ case "NOTEOFF": return MIDIEvent.NOTEON; break;
+ case "KEY PRESSURE":
+ case "KEYPRESSURE": return MIDIEvent.KEYPRESSURE; break;
+ case "CONTROLLER CHANGE":
+ case "CONTROLLERCHANGE":
+ case "CC": return MIDIEvent.CONTROLLERCHANGE; break;
+ case "PROGRAM CHANGE":
+ case "PROGRAMCHANGE":
+ case "PC": return MIDIEvent.PROGRAMCHANGE; break;
+ case "CHANNEL PRESSURE":
+ case "CHANNELPRESSURE": return MIDIEvent.CHANNELPRESSURE; break;
+ case "PITCH BEND":
+ case "PITCHBEND": return MIDIEvent.PITCHBEND; break;
+ case "TIME TICK":
+ case "TIMETICK": return MIDIEvent.TIMETICK; break;
+ default: return Number(str); //asume its a hex code
+ }
+}
+
+MIDIEvent.toNoteString = function(d)
+{
+ var note = d - 21;
+ var octave = d - 24;
+ note = note % 12;
+ if(note < 0)
+ note = 12 + note;
+ return MIDIEvent.notes[ note ] + Math.floor(octave / 12 + 1);
+}
+
+MIDIEvent.prototype.toString = function()
+{
+ var str = "" + this.channel + ". " ;
+ switch( this.cmd )
+ {
+ case MIDIEvent.NOTEON: str += "NOTEON " + MIDIEvent.toNoteString( this.data[1] ); break;
+ case MIDIEvent.NOTEOFF: str += "NOTEOFF " + MIDIEvent.toNoteString( this.data[1] ); break;
+ case MIDIEvent.CONTROLLERCHANGE: str += "CC " + this.data[1] + " " + this.data[2]; break;
+ case MIDIEvent.PROGRAMCHANGE: str += "PC " + this.data[1]; break;
+ case MIDIEvent.PITCHBEND: str += "PITCHBEND " + this.getPitchBend(); break;
+ case MIDIEvent.KEYPRESSURE: str += "KEYPRESS " + this.data[1]; break;
+ }
+
+ return str;
+}
+
+MIDIEvent.prototype.toHexString = function()
+{
+ var str = "";
+ for(var i = 0; i < this.data.length; i++)
+ str += this.data[i].toString(16) + " ";
+}
+
+MIDIEvent.NOTEOFF = 0x80;
+MIDIEvent.NOTEON = 0x90;
+MIDIEvent.KEYPRESSURE = 0xA0;
+MIDIEvent.CONTROLLERCHANGE = 0xB0;
+MIDIEvent.PROGRAMCHANGE = 0xC0;
+MIDIEvent.CHANNELPRESSURE = 0xD0;
+MIDIEvent.PITCHBEND = 0xE0;
+MIDIEvent.TIMETICK = 0xF8;
+
+MIDIEvent.commands = {
+ 0x80: "note off",
+ 0x90: "note on",
+ 0xA0: "key pressure",
+ 0xB0: "controller change",
+ 0xC0: "program change",
+ 0xD0: "channel pressure",
+ 0xE0: "pitch bend",
+ 0xF0: "system",
+ 0xF2: "Song pos",
+ 0xF3: "Song select",
+ 0xF6: "Tune request",
+ 0xF8: "time tick",
+ 0xFA: "Start Song",
+ 0xFB: "Continue Song",
+ 0xFC: "Stop Song",
+ 0xFE: "Sensing",
+ 0xFF: "Reset"
+}
+
+//MIDI wrapper
+function MIDIInterface( on_ready, on_error )
+{
+ if(!navigator.requestMIDIAccess)
+ {
+ this.error = "not suppoorted";
+ if(on_error)
+ on_error("Not supported");
+ else
+ console.error("MIDI NOT SUPPORTED, enable by chrome://flags");
+ return;
+ }
+
+ this.on_ready = on_ready;
+
+ this.state = {
+ note: [],
+ cc: []
+ };
+
+
+
+ navigator.requestMIDIAccess().then( this.onMIDISuccess.bind(this), this.onMIDIFailure.bind(this) );
+}
+
+MIDIInterface.input = null;
+
+MIDIInterface.MIDIEvent = MIDIEvent;
+
+MIDIInterface.prototype.onMIDISuccess = function(midiAccess)
+{
+ console.log( "MIDI ready!" );
+ console.log( midiAccess );
+ this.midi = midiAccess; // store in the global (in real usage, would probably keep in an object instance)
+ this.updatePorts();
+
+ if (this.on_ready)
+ this.on_ready(this);
+}
+
+MIDIInterface.prototype.updatePorts = function()
+{
+ var midi = this.midi;
+ this.input_ports = midi.inputs;
+ var num = 0;
+
+ var it = this.input_ports.values();
+ var it_value = it.next();
+ while( it_value && it_value.done === false )
+ {
+ var port_info = it_value.value;
+ console.log( "Input port [type:'" + port_info.type + "'] id:'" + port_info.id +
+ "' manufacturer:'" + port_info.manufacturer + "' name:'" + port_info.name +
+ "' version:'" + port_info.version + "'" );
+ num++;
+ it_value = it.next();
+ }
+ this.num_input_ports = num;
+
+ num = 0;
+ this.output_ports = midi.outputs;
+ var it = this.output_ports.values();
+ var it_value = it.next();
+ while( it_value && it_value.done === false )
+ {
+ var port_info = it_value.value;
+ console.log( "Output port [type:'" + port_info.type + "'] id:'" + port_info.id +
+ "' manufacturer:'" + port_info.manufacturer + "' name:'" + port_info.name +
+ "' version:'" + port_info.version + "'" );
+ num++;
+ it_value = it.next();
+ }
+ this.num_output_ports = num;
+
+
+ /* OLD WAY
+ for (var i = 0; i < this.input_ports.size; ++i) {
+ var input = this.input_ports.get(i);
+ if(!input)
+ continue; //sometimes it is null?!
+ console.log( "Input port [type:'" + input.type + "'] id:'" + input.id +
+ "' manufacturer:'" + input.manufacturer + "' name:'" + input.name +
+ "' version:'" + input.version + "'" );
+ num++;
+ }
+ this.num_input_ports = num;
+
+
+ num = 0;
+ this.output_ports = midi.outputs;
+ for (var i = 0; i < this.output_ports.size; ++i) {
+ var output = this.output_ports.get(i);
+ if(!output)
+ continue;
+ console.log( "Output port [type:'" + output.type + "'] id:'" + output.id +
+ "' manufacturer:'" + output.manufacturer + "' name:'" + output.name +
+ "' version:'" + output.version + "'" );
+ num++;
+ }
+ this.num_output_ports = num;
+ */
+}
+
+MIDIInterface.prototype.onMIDIFailure = function(msg)
+{
+ console.error( "Failed to get MIDI access - " + msg );
+}
+
+MIDIInterface.prototype.openInputPort = function( port, callback )
+{
+ var input_port = this.input_ports.get( "input-" + port );
+ if(!input_port)
+ return false;
+ MIDIInterface.input = this;
+ var that = this;
+
+ input_port.onmidimessage = function(a) {
+ var midi_event = new MIDIEvent(a.data);
+ that.updateState( midi_event );
+ if(callback)
+ callback(a.data, midi_event );
+ if(MIDIInterface.on_message)
+ MIDIInterface.on_message( a.data, midi_event );
+ }
+ console.log("port open: ", input_port);
+ return true;
+}
+
+MIDIInterface.parseMsg = function(data)
+{
+
+}
+
+MIDIInterface.prototype.updateState = function( midi_event )
+{
+ switch( midi_event.cmd )
+ {
+ case MIDIEvent.NOTEON: this.state.note[ midi_event.value1|0 ] = midi_event.value2; break;
+ case MIDIEvent.NOTEOFF: this.state.note[ midi_event.value1|0 ] = 0; break;
+ case MIDIEvent.CONTROLLERCHANGE: this.state.cc[ midi_event.getCC() ] = midi_event.getCCValue(); break;
+ }
+}
+
+MIDIInterface.prototype.sendMIDI = function( port, midi_data )
+{
+ if( !midi_data )
+ return;
+
+ var output_port = this.output_ports.get( "output-" + port );
+ if(!output_port)
+ return;
+
+ MIDIInterface.output = this;
+
+ if( midi_data.constructor === MIDIEvent)
+ output_port.send( midi_data.data );
+ else
+ output_port.send( midi_data );
+}
+
+
+
+function LGMIDIIn()
+{
+ this.addOutput( "on_midi", LiteGraph.EVENT );
+ this.addOutput( "out", "midi" );
+ this.properties = {port: 0};
+ this._last_midi_event = null;
+ this._current_midi_event = null;
+
+ var that = this;
+ new MIDIInterface( function( midi ){
+ //open
+ that._midi = midi;
+ if(that._waiting)
+ that.onStart();
+ that._waiting = false;
+ });
+}
+
+LGMIDIIn.MIDIInterface = MIDIInterface;
+
+LGMIDIIn.title = "MIDI Input";
+LGMIDIIn.desc = "Reads MIDI from a input port";
+
+LGMIDIIn.prototype.getPropertyInfo = function(name)
+{
+ if(!this._midi)
+ return;
+
+ if(name == "port")
+ {
+ var values = {};
+ for (var i = 0; i < this._midi.input_ports.size; ++i)
+ {
+ var input = this._midi.input_ports.get( "input-" + i);
+ values[i] = i + ".- " + input.name + " version:" + input.version;
+ }
+ return { type: "enum", values: values };
+ }
+}
+
+LGMIDIIn.prototype.onStart = function()
+{
+ if(this._midi)
+ this._midi.openInputPort( this.properties.port, this.onMIDIEvent.bind(this) );
+ else
+ this._waiting = true;
+}
+
+LGMIDIIn.prototype.onMIDIEvent = function( data, midi_event )
+{
+ this._last_midi_event = midi_event;
+
+ this.trigger( "on_midi", midi_event );
+ if(midi_event.cmd == MIDIEvent.NOTEON)
+ this.trigger( "on_noteon", midi_event );
+ else if(midi_event.cmd == MIDIEvent.NOTEOFF)
+ this.trigger( "on_noteoff", midi_event );
+ else if(midi_event.cmd == MIDIEvent.CONTROLLERCHANGE)
+ this.trigger( "on_cc", midi_event );
+ else if(midi_event.cmd == MIDIEvent.PROGRAMCHANGE)
+ this.trigger( "on_pc", midi_event );
+ else if(midi_event.cmd == MIDIEvent.PITCHBEND)
+ this.trigger( "on_pitchbend", midi_event );
+}
+
+LGMIDIIn.prototype.onExecute = function()
+{
+ if(this.outputs)
+ {
+ var last = this._last_midi_event;
+ for(var i = 0; i < this.outputs.length; ++i)
+ {
+ var output = this.outputs[i];
+ var v = null;
+ switch (output.name)
+ {
+ case "midi": v = this._midi; break;
+ case "last_midi": v = last; break;
+ default:
+ continue;
+ }
+ this.setOutputData( i, v );
+ }
+ }
+}
+
+LGMIDIIn.prototype.onGetOutputs = function() {
+ return [
+ ["last_midi","midi"],
+ ["on_midi",LiteGraph.EVENT],
+ ["on_noteon",LiteGraph.EVENT],
+ ["on_noteoff",LiteGraph.EVENT],
+ ["on_cc",LiteGraph.EVENT],
+ ["on_pc",LiteGraph.EVENT],
+ ["on_pitchbend",LiteGraph.EVENT]
+ ];
+}
+
+LiteGraph.registerNodeType("midi/input", LGMIDIIn);
+
+
+function LGMIDIOut()
+{
+ this.addInput( "send", LiteGraph.EVENT );
+ this.properties = {port: 0};
+
+ var that = this;
+ new MIDIInterface( function( midi ){
+ that._midi = midi;
+ });
+}
+
+LGMIDIOut.MIDIInterface = MIDIInterface;
+
+LGMIDIOut.title = "MIDI Output";
+LGMIDIOut.desc = "Sends MIDI to output channel";
+
+LGMIDIOut.prototype.getPropertyInfo = function(name)
+{
+ if(!this._midi)
+ return;
+
+ if(name == "port")
+ {
+ var values = {};
+ for (var i = 0; i < this._midi.output_ports.size; ++i)
+ {
+ var output = this._midi.output_ports.get(i);
+ values[i] = i + ".- " + output.name + " version:" + output.version;
+ }
+ return { type: "enum", values: values };
+ }
+}
+
+
+LGMIDIOut.prototype.onAction = function(event, midi_event )
+{
+ console.log(midi_event);
+ if(!this._midi)
+ return;
+ if(event == "send")
+ this._midi.sendMIDI( this.port, midi_event );
+ this.trigger("midi",midi_event);
+}
+
+LGMIDIOut.prototype.onGetInputs = function() {
+ return [["send",LiteGraph.ACTION]];
+}
+
+LGMIDIOut.prototype.onGetOutputs = function() {
+ return [["on_midi",LiteGraph.EVENT]];
+}
+
+LiteGraph.registerNodeType("midi/output", LGMIDIOut);
+
+
+function LGMIDIShow()
+{
+ this.addInput( "on_midi", LiteGraph.EVENT );
+ this._str = "";
+ this.size = [200,40]
+}
+
+LGMIDIShow.title = "MIDI Show";
+LGMIDIShow.desc = "Shows MIDI in the graph";
+
+LGMIDIShow.prototype.onAction = function(event, midi_event )
+{
+ if(!midi_event)
+ return;
+ if(midi_event.constructor === MIDIEvent)
+ this._str = midi_event.toString();
+ else
+ this._str = "???";
+}
+
+LGMIDIShow.prototype.onDrawForeground = function( ctx )
+{
+ if( !this._str )
+ return;
+
+ ctx.font = "30px Arial";
+ ctx.fillText( this._str, 10, this.size[1] * 0.8 );
+}
+
+LGMIDIShow.prototype.onGetInputs = function() {
+ return [["in",LiteGraph.ACTION]];
+}
+
+LGMIDIShow.prototype.onGetOutputs = function() {
+ return [["on_midi",LiteGraph.EVENT]];
+}
+
+LiteGraph.registerNodeType("midi/show", LGMIDIShow);
+
+
+
+function LGMIDIFilter()
+{
+ this.properties = {
+ channel: -1,
+ cmd: -1,
+ min_value: -1,
+ max_value: -1
+ };
+
+ this.addInput( "in", LiteGraph.EVENT );
+ this.addOutput( "on_midi", LiteGraph.EVENT );
+}
+
+LGMIDIFilter.title = "MIDI Filter";
+LGMIDIFilter.desc = "Filters MIDI messages";
+
+LGMIDIFilter.prototype.onAction = function(event, midi_event )
+{
+ if(!midi_event || midi_event.constructor !== MIDIEvent)
+ return;
+
+ if( this.properties.channel != -1 && midi_event.channel != this.properties.channel)
+ return;
+ if(this.properties.cmd != -1 && midi_event.cmd != this.properties.cmd)
+ return;
+ if(this.properties.min_value != -1 && midi_event.data[1] < this.properties.min_value)
+ return;
+ if(this.properties.max_value != -1 && midi_event.data[1] > this.properties.max_value)
+ return;
+ this.trigger("on_midi",midi_event);
+}
+
+LiteGraph.registerNodeType("midi/filter", LGMIDIFilter);
+
+
+function LGMIDIEvent()
+{
+ this.properties = {
+ channel: 0,
+ cmd: "CC",
+ value1: 1,
+ value2: 1
+ };
+
+ this.addInput( "send", LiteGraph.EVENT );
+ this.addInput( "assign", LiteGraph.EVENT );
+ this.addOutput( "on_midi", LiteGraph.EVENT );
+}
+
+LGMIDIEvent.title = "MIDIEvent";
+LGMIDIEvent.desc = "Create a MIDI Event";
+
+LGMIDIEvent.prototype.onAction = function( event, midi_event )
+{
+ if(event == "assign")
+ {
+ this.properties.channel = midi_event.channel;
+ this.properties.cmd = midi_event.cmd;
+ this.properties.value1 = midi_event.data[1];
+ this.properties.value2 = midi_event.data[2];
+ return;
+ }
+
+ //send
+ var midi_event = new MIDIEvent();
+ midi_event.channel = this.properties.channel;
+ if(this.properties.cmd && this.properties.cmd.constructor === String)
+ midi_event.setCommandFromString( this.properties.cmd );
+ else
+ midi_event.cmd = this.properties.cmd;
+ midi_event.data[0] = midi_event.cmd | midi_event.channel;
+ midi_event.data[1] = Number(this.properties.value1);
+ midi_event.data[2] = Number(this.properties.value2);
+ this.trigger("on_midi",midi_event);
+}
+
+LGMIDIEvent.prototype.onExecute = function()
+{
+ var props = this.properties;
+
+ if(this.outputs)
+ {
+ for(var i = 0; i < this.outputs.length; ++i)
+ {
+ var output = this.outputs[i];
+ var v = null;
+ switch (output.name)
+ {
+ case "midi":
+ v = new MIDIEvent();
+ v.setup([ props.cmd, props.value1, props.value2 ]);
+ v.channel = props.channel;
+ break;
+ case "command": v = props.cmd; break;
+ case "cc": v = props.value1; break;
+ case "cc_value": v = props.value2; break;
+ case "note": v = (props.cmd == MIDIEvent.NOTEON || props.cmd == MIDIEvent.NOTEOFF) ? props.value1 : null; break;
+ case "velocity": v = props.cmd == MIDIEvent.NOTEON ? props.value2 : null; break;
+ case "pitch": v = props.cmd == MIDIEvent.NOTEON ? MIDIEvent.computePitch( props.value1 ) : null; break;
+ case "pitchbend": v = props.cmd == MIDIEvent.PITCHBEND ? MIDIEvent.computePitchBend( props.value1, props.value2 ) : null; break;
+ default:
+ continue;
+ }
+ if(v !== null)
+ this.setOutputData( i, v );
+ }
+ }
+}
+
+LGMIDIEvent.prototype.onPropertyChanged = function(name,value)
+{
+ if(name == "cmd")
+ this.properties.cmd = MIDIEvent.computeCommandFromString( value );
+}
+
+
+LGMIDIEvent.prototype.onGetOutputs = function() {
+ return [
+ ["midi","midi"],
+ ["on_midi",LiteGraph.EVENT],
+ ["command","number"],
+ ["note","number"],
+ ["velocity","number"],
+ ["cc","number"],
+ ["cc_value","number"],
+ ["pitch","number"],
+ ["pitchbend","number"]
+ ];
+}
+
+
+LiteGraph.registerNodeType("midi/event", LGMIDIEvent);
+
+
+function LGMIDICC()
+{
+ this.properties = {
+// channel: 0,
+ cc: 1,
+ value: 0
+ };
+
+ this.addOutput( "value", "number" );
+}
+
+LGMIDICC.title = "MIDICC";
+LGMIDICC.desc = "gets a Controller Change";
+
+LGMIDICC.prototype.onExecute = function()
+{
+ var props = this.properties;
+ if( MIDIInterface.input )
+ this.properties.value = MIDIInterface.input.state.cc[ this.properties.cc ];
+ this.setOutputData( 0, this.properties.value );
+}
+
+LiteGraph.registerNodeType("midi/cc", LGMIDICC);
+
+
+
+
+function now() { return window.performance.now() }
+
})( this );
-(function( global )
-{
-var LiteGraph = global.LiteGraph;
-
-var LGAudio = {};
-global.LGAudio = LGAudio;
-
-LGAudio.getAudioContext = function()
-{
- if(!this._audio_context)
- {
- window.AudioContext = window.AudioContext || window.webkitAudioContext;
- if(!window.AudioContext)
- {
- console.error("AudioContext not supported by browser");
- return null;
- }
- this._audio_context = new AudioContext();
- this._audio_context.onmessage = function(msg) { console.log("msg",msg);};
- this._audio_context.onended = function(msg) { console.log("ended",msg);};
- this._audio_context.oncomplete = function(msg) { console.log("complete",msg);};
- }
-
- //in case it crashes
- //if(this._audio_context.state == "suspended")
- // this._audio_context.resume();
- return this._audio_context;
-}
-
-LGAudio.connect = function( audionodeA, audionodeB )
-{
- try
- {
- audionodeA.connect( audionodeB );
- }
- catch (err)
- {
- console.warn("LGraphAudio:",err);
- }
-}
-
-LGAudio.disconnect = function( audionodeA, audionodeB )
-{
- try
- {
- audionodeA.disconnect( audionodeB );
- }
- catch (err)
- {
- console.warn("LGraphAudio:",err);
- }
-}
-
-LGAudio.changeAllAudiosConnections = function( node, connect )
-{
- if(node.inputs)
- {
- for(var i = 0; i < node.inputs.length; ++i)
- {
- var input = node.inputs[i];
- var link_info = node.graph.links[ input.link ];
- if(!link_info)
- continue;
-
- var origin_node = node.graph.getNodeById( link_info.origin_id );
- var origin_audionode = null;
- if( origin_node.getAudioNodeInOutputSlot )
- origin_audionode = origin_node.getAudioNodeInOutputSlot( link_info.origin_slot );
- else
- origin_audionode = origin_node.audionode;
-
- var target_audionode = null;
- if( node.getAudioNodeInInputSlot )
- target_audionode = node.getAudioNodeInInputSlot( i );
- else
- target_audionode = node.audionode;
-
- if(connect)
- LGAudio.connect( origin_audionode, target_audionode );
- else
- LGAudio.disconnect( origin_audionode, target_audionode );
- }
- }
-
- if(node.outputs)
- {
- for(var i = 0; i < node.outputs.length; ++i)
- {
- var output = node.outputs[i];
- for(var j = 0; j < output.links.length; ++j)
- {
- var link_info = node.graph.links[ output.links[j] ];
- if(!link_info)
- continue;
-
- var origin_audionode = null;
- if( node.getAudioNodeInOutputSlot )
- origin_audionode = node.getAudioNodeInOutputSlot( i );
- else
- origin_audionode = node.audionode;
-
- var target_node = node.graph.getNodeById( link_info.target_id );
- var target_audionode = null;
- if( target_node.getAudioNodeInInputSlot )
- target_audionode = target_node.getAudioNodeInInputSlot( link_info.target_slot );
- else
- target_audionode = target_node.audionode;
-
- if(connect)
- LGAudio.connect( origin_audionode, target_audionode );
- else
- LGAudio.disconnect( origin_audionode, target_audionode );
- }
- }
- }
-}
-
-//used by many nodes
-LGAudio.onConnectionsChange = function( connection, slot, connected, link_info )
-{
- //only process the outputs events
- if(connection != LiteGraph.OUTPUT)
- return;
-
- var target_node = null;
- if( link_info )
- target_node = this.graph.getNodeById( link_info.target_id );
-
- if( !target_node )
- return;
-
- //get origin audionode
- var local_audionode = null;
- if(this.getAudioNodeInOutputSlot)
- local_audionode = this.getAudioNodeInOutputSlot( slot );
- else
- local_audionode = this.audionode;
-
- //get target audionode
- var target_audionode = null;
- if(target_node.getAudioNodeInInputSlot)
- target_audionode = target_node.getAudioNodeInInputSlot( link_info.target_slot );
- else
- target_audionode = target_node.audionode;
-
- //do the connection/disconnection
- if( connected )
- LGAudio.connect( local_audionode, target_audionode );
- else
- LGAudio.disconnect( local_audionode, target_audionode );
-}
-
-//this function helps creating wrappers to existing classes
-LGAudio.createAudioNodeWrapper = function( class_object )
-{
- var old_func = class_object.prototype.onPropertyChanged;
-
- class_object.prototype.onPropertyChanged = function(name, value)
- {
- if(old_func)
- old_func.call(this,name,value);
-
- if(!this.audionode)
- return;
-
- if( this.audionode[ name ] === undefined )
- return;
-
- if( this.audionode[ name ].value !== undefined )
- this.audionode[ name ].value = value;
- else
- this.audionode[ name ] = value;
- }
-
- class_object.prototype.onConnectionsChange = LGAudio.onConnectionsChange;
-}
-
-//contains the samples decoded of the loaded audios in AudioBuffer format
-LGAudio.cached_audios = {};
-
-LGAudio.loadSound = function( url, on_complete, on_error )
-{
- if( LGAudio.cached_audios[ url ] && url.indexOf("blob:") == -1 )
- {
- if(on_complete)
- on_complete( LGAudio.cached_audios[ url ] );
- return;
- }
-
- if( LGAudio.onProcessAudioURL )
- url = LGAudio.onProcessAudioURL( url );
-
- //load new sample
- var request = new XMLHttpRequest();
- request.open('GET', url, true);
- request.responseType = 'arraybuffer';
-
- var context = LGAudio.getAudioContext();
-
- // Decode asynchronously
- request.onload = function() {
- console.log("AudioSource loaded");
- context.decodeAudioData( request.response, function( buffer ) {
- console.log("AudioSource decoded");
- LGAudio.cached_audios[ url ] = buffer;
- if(on_complete)
- on_complete( buffer );
- }, onError);
- }
- request.send();
-
- function onError(err)
- {
- console.log("Audio loading sample error:",err);
- if(on_error)
- on_error(err);
- }
-
- return request;
-}
-
-
-//****************************************************
-
-function LGAudioSource()
-{
- this.properties = {
- src: "",
- gain: 0.5,
- loop: true,
- autoplay: true,
- playbackRate: 1
- };
-
- this._loading_audio = false;
- this._audiobuffer = null; //points to AudioBuffer with the audio samples decoded
- this._audionodes = [];
- this._last_sourcenode = null; //the last AudioBufferSourceNode (there could be more if there are several sounds playing)
-
- this.addOutput( "out", "audio" );
- this.addInput( "gain", "number" );
-
- //init context
- var context = LGAudio.getAudioContext();
-
- //create gain node to control volume
- this.audionode = context.createGain();
- this.audionode.graphnode = this;
- this.audionode.gain.value = this.properties.gain;
-
- //debug
- if(this.properties.src)
- this.loadSound( this.properties.src );
-}
-
-LGAudioSource["@src"] = { widget: "resource" };
-LGAudioSource.supported_extensions = ["wav","ogg","mp3"];
-
-
-LGAudioSource.prototype.onAdded = function(graph)
-{
- if(graph.status === LGraph.STATUS_RUNNING)
- this.onStart();
-}
-
-LGAudioSource.prototype.onStart = function()
-{
- if(!this._audiobuffer)
- return;
-
- if(this.properties.autoplay)
- this.playBuffer( this._audiobuffer );
-}
-
-LGAudioSource.prototype.onStop = function()
-{
- this.stopAllSounds();
-}
-
-LGAudioSource.prototype.onPause = function()
-{
- this.pauseAllSounds();
-}
-
-LGAudioSource.prototype.onUnpause = function()
-{
- this.unpauseAllSounds();
- //this.onStart();
-}
-
-
-LGAudioSource.prototype.onRemoved = function()
-{
- this.stopAllSounds();
- if(this._dropped_url)
- URL.revokeObjectURL( this._url );
-}
-
-LGAudioSource.prototype.stopAllSounds = function()
-{
- //iterate and stop
- for(var i = 0; i < this._audionodes.length; ++i )
- {
- if(this._audionodes[i].started)
- {
- this._audionodes[i].started = false;
- this._audionodes[i].stop();
- }
- //this._audionodes[i].disconnect( this.audionode );
- }
- this._audionodes.length = 0;
-}
-
-LGAudioSource.prototype.pauseAllSounds = function()
-{
- LGAudio.getAudioContext().suspend();
-}
-
-LGAudioSource.prototype.unpauseAllSounds = function()
-{
- LGAudio.getAudioContext().resume();
-}
-
-LGAudioSource.prototype.onExecute = function()
-{
- if(this.inputs)
- for(var i = 0; i < this.inputs.length; ++i)
- {
- var input = this.inputs[i];
- if(input.link == null)
- continue;
- var v = this.getInputData(i);
- if( v === undefined )
- continue;
- if( input.name == "gain" )
- this.audionode.gain.value = v;
- else if( input.name == "playbackRate" )
- {
- this.properties.playbackRate = v;
- for(var j = 0; j < this._audionodes.length; ++j)
- this._audionodes[j].playbackRate.value = v;
- }
- }
-
- if(this.outputs)
- for(var i = 0; i < this.outputs.length; ++i)
- {
- var output = this.outputs[i];
- if( output.name == "buffer" && this._audiobuffer )
- this.setOutputData( i, this._audiobuffer );
- }
-}
-
-LGAudioSource.prototype.onAction = function(event)
-{
- if(this._audiobuffer)
- {
- if(event == "Play")
- this.playBuffer(this._audiobuffer);
- else if(event == "Stop")
- this.stopAllSounds();
- }
-}
-
-LGAudioSource.prototype.onPropertyChanged = function( name, value )
-{
- if( name == "src" )
- this.loadSound( value );
- else if(name == "gain")
- this.audionode.gain.value = value;
- else if(name == "playbackRate")
- {
- for(var j = 0; j < this._audionodes.length; ++j)
- this._audionodes[j].playbackRate.value = value;
- }
-}
-
-LGAudioSource.prototype.playBuffer = function( buffer )
-{
- var that = this;
- var context = LGAudio.getAudioContext();
-
- //create a new audionode (this is mandatory, AudioAPI doesnt like to reuse old ones)
- var audionode = context.createBufferSource(); //create a AudioBufferSourceNode
- this._last_sourcenode = audionode;
- audionode.graphnode = this;
- audionode.buffer = buffer;
- audionode.loop = this.properties.loop;
- audionode.playbackRate.value = this.properties.playbackRate;
- this._audionodes.push( audionode );
- audionode.connect( this.audionode ); //connect to gain
- this._audionodes.push( audionode );
-
- audionode.onended = function()
- {
- //console.log("ended!");
- that.trigger("ended");
- //remove
- var index = that._audionodes.indexOf( audionode );
- if(index != -1)
- that._audionodes.splice(index,1);
- }
-
- if(!audionode.started)
- {
- audionode.started = true;
- audionode.start();
- }
- return audionode;
-}
-
-LGAudioSource.prototype.loadSound = function( url )
-{
- var that = this;
-
- //kill previous load
- if(this._request)
- {
- this._request.abort();
- this._request = null;
- }
-
- this._audiobuffer = null; //points to the audiobuffer once the audio is loaded
- this._loading_audio = false;
-
- if(!url)
- return;
-
- this._request = LGAudio.loadSound( url, inner );
-
- this._loading_audio = true;
- this.boxcolor = "#AA4";
-
- function inner( buffer )
- {
- this.boxcolor = LiteGraph.NODE_DEFAULT_BOXCOLOR;
- that._audiobuffer = buffer;
- that._loading_audio = false;
- //if is playing, then play it
- if(that.graph && that.graph.status === LGraph.STATUS_RUNNING)
- that.onStart(); //this controls the autoplay already
- }
-}
-
-//Helps connect/disconnect AudioNodes when new connections are made in the node
-LGAudioSource.prototype.onConnectionsChange = LGAudio.onConnectionsChange;
-
-LGAudioSource.prototype.onGetInputs = function()
-{
- return [["playbackRate","number"],["Play",LiteGraph.ACTION],["Stop",LiteGraph.ACTION]];
-}
-
-LGAudioSource.prototype.onGetOutputs = function()
-{
- return [["buffer","audiobuffer"],["ended",LiteGraph.EVENT]];
-}
-
-LGAudioSource.prototype.onDropFile = function(file)
-{
- if(this._dropped_url)
- URL.revokeObjectURL( this._dropped_url );
- var url = URL.createObjectURL( file );
- this.properties.src = url;
- this.loadSound( url );
- this._dropped_url = url;
-}
-
-
-LGAudioSource.title = "Source";
-LGAudioSource.desc = "Plays audio";
-LiteGraph.registerNodeType("audio/source", LGAudioSource);
-
-
-//*****************************************************
-
-function LGAudioAnalyser()
-{
- this.properties = {
- fftSize: 2048,
- minDecibels: -100,
- maxDecibels: -10,
- smoothingTimeConstant: 0.5
- };
-
- var context = LGAudio.getAudioContext();
-
- this.audionode = context.createAnalyser();
- this.audionode.graphnode = this;
- this.audionode.fftSize = this.properties.fftSize;
- this.audionode.minDecibels = this.properties.minDecibels;
- this.audionode.maxDecibels = this.properties.maxDecibels;
- this.audionode.smoothingTimeConstant = this.properties.smoothingTimeConstant;
-
- this.addInput("in","audio");
- this.addOutput("freqs","array");
- this.addOutput("samples","array");
-
- this._freq_bin = null;
- this._time_bin = null;
-}
-
-LGAudioAnalyser.prototype.onPropertyChanged = function(name, value)
-{
- this.audionode[ name ] = value;
-}
-
-LGAudioAnalyser.prototype.onExecute = function()
-{
- if(this.isOutputConnected(0))
- {
- //send FFT
- var bufferLength = this.audionode.frequencyBinCount;
- if( !this._freq_bin || this._freq_bin.length != bufferLength )
- this._freq_bin = new Uint8Array( bufferLength );
- this.audionode.getByteFrequencyData( this._freq_bin );
- this.setOutputData(0,this._freq_bin);
- }
-
- //send analyzer
- if(this.isOutputConnected(1))
- {
- //send Samples
- var bufferLength = this.audionode.frequencyBinCount;
- if( !this._time_bin || this._time_bin.length != bufferLength )
- this._time_bin = new Uint8Array( bufferLength );
- this.audionode.getByteTimeDomainData( this._time_bin );
- this.setOutputData(1,this._time_bin);
- }
-
-
- //properties
- for(var i = 1; i < this.inputs.length; ++i)
- {
- var input = this.inputs[i];
- if(input.link == null)
- continue;
- var v = this.getInputData(i);
- if (v !== undefined)
- this.audionode[ input.name ].value = v;
- }
-
-
-
- //time domain
- //this.audionode.getFloatTimeDomainData( dataArray );
-}
-
-LGAudioAnalyser.prototype.onGetInputs = function()
-{
- return [["minDecibels","number"],["maxDecibels","number"],["smoothingTimeConstant","number"]];
-}
-
-LGAudioAnalyser.prototype.onGetOutputs = function()
-{
- return [["freqs","array"],["samples","array"]];
-}
-
-
-LGAudioAnalyser.title = "Analyser";
-LGAudioAnalyser.desc = "Audio Analyser";
-LiteGraph.registerNodeType( "audio/analyser", LGAudioAnalyser );
-
-//*****************************************************
-
-function LGAudioGain()
-{
- //default
- this.properties = {
- gain: 1
- };
-
- this.audionode = LGAudio.getAudioContext().createGain();
- this.addInput("in","audio");
- this.addInput("gain","number");
- this.addOutput("out","audio");
-}
-
-LGAudioGain.prototype.onExecute = function()
-{
- if(!this.inputs || !this.inputs.length)
- return;
-
- for(var i = 1; i < this.inputs.length; ++i)
- {
- var input = this.inputs[i];
- var v = this.getInputData(i);
- if(v !== undefined)
- this.audionode[ input.name ].value = v;
- }
-}
-
-LGAudio.createAudioNodeWrapper( LGAudioGain );
-
-LGAudioGain.title = "Gain";
-LGAudioGain.desc = "Audio gain";
-LiteGraph.registerNodeType("audio/gain", LGAudioGain);
-
-
-function LGAudioConvolver()
-{
- //default
- this.properties = {
- impulse_src:"",
- normalize: true
- };
-
- this.audionode = LGAudio.getAudioContext().createConvolver();
- this.addInput("in","audio");
- this.addOutput("out","audio");
-}
-
-LGAudio.createAudioNodeWrapper( LGAudioConvolver );
-
-LGAudioConvolver.prototype.onRemove = function()
-{
- if(this._dropped_url)
- URL.revokeObjectURL( this._dropped_url );
-}
-
-LGAudioConvolver.prototype.onPropertyChanged = function( name, value )
-{
- if( name == "impulse_src" )
- this.loadImpulse( value );
- else if( name == "normalize" )
- this.audionode.normalize = value;
-}
-
-LGAudioConvolver.prototype.onDropFile = function(file)
-{
- if(this._dropped_url)
- URL.revokeObjectURL( this._dropped_url );
- this._dropped_url = URL.createObjectURL( file );
- this.properties.impulse_src = this._dropped_url;
- this.loadImpulse( this._dropped_url );
-}
-
-LGAudioConvolver.prototype.loadImpulse = function( url )
-{
- var that = this;
-
- //kill previous load
- if(this._request)
- {
- this._request.abort();
- this._request = null;
- }
-
- this._impulse_buffer = null;
- this._loading_impulse = false;
-
- if(!url)
- return;
-
- //load new sample
- this._request = LGAudio.loadSound( url, inner );
- this._loading_impulse = true;
-
- // Decode asynchronously
- function inner( buffer ) {
- that._impulse_buffer = buffer;
- that.audionode.buffer = buffer;
- console.log("Impulse signal set");
- that._loading_impulse = false;
- }
-}
-
-LGAudioConvolver.title = "Convolver";
-LGAudioConvolver.desc = "Convolves the signal (used for reverb)";
-LiteGraph.registerNodeType("audio/convolver", LGAudioConvolver);
-
-
-function LGAudioDynamicsCompressor()
-{
- //default
- this.properties = {
- threshold: -50,
- knee: 40,
- ratio: 12,
- reduction: -20,
- attack: 0,
- release: 0.25
- };
-
- this.audionode = LGAudio.getAudioContext().createDynamicsCompressor();
- this.addInput("in","audio");
- this.addOutput("out","audio");
-}
-
-LGAudio.createAudioNodeWrapper( LGAudioDynamicsCompressor );
-
-LGAudioDynamicsCompressor.prototype.onExecute = function()
-{
- if(!this.inputs || !this.inputs.length)
- return;
- for(var i = 1; i < this.inputs.length; ++i)
- {
- var input = this.inputs[i];
- if(input.link == null)
- continue;
- var v = this.getInputData(i);
- if(v !== undefined)
- this.audionode[ input.name ].value = v;
- }
-}
-
-LGAudioDynamicsCompressor.prototype.onGetInputs = function()
-{
- return [["threshold","number"],["knee","number"],["ratio","number"],["reduction","number"],["attack","number"],["release","number"]];
-}
-
-LGAudioDynamicsCompressor.title = "DynamicsCompressor";
-LGAudioDynamicsCompressor.desc = "Dynamics Compressor";
-LiteGraph.registerNodeType("audio/dynamicsCompressor", LGAudioDynamicsCompressor);
-
-
-function LGAudioWaveShaper()
-{
- //default
- this.properties = {
- };
-
- this.audionode = LGAudio.getAudioContext().createWaveShaper();
- this.addInput("in","audio");
- this.addInput("shape","waveshape");
- this.addOutput("out","audio");
-}
-
-LGAudioWaveShaper.prototype.onExecute = function()
-{
- if(!this.inputs || !this.inputs.length)
- return;
- var v = this.getInputData(1);
- if(v === undefined)
- return;
- this.audionode.curve = v;
-}
-
-LGAudioWaveShaper.prototype.setWaveShape = function(shape)
-{
- this.audionode.curve = shape;
-}
-
-LGAudio.createAudioNodeWrapper( LGAudioWaveShaper );
-
-/* disabled till I dont find a way to do a wave shape
-LGAudioWaveShaper.title = "WaveShaper";
-LGAudioWaveShaper.desc = "Distortion using wave shape";
-LiteGraph.registerNodeType("audio/waveShaper", LGAudioWaveShaper);
-*/
-
-function LGAudioMixer()
-{
- //default
- this.properties = {
- gain1: 0.5,
- gain2: 0.5
- };
-
- this.audionode = LGAudio.getAudioContext().createGain();
-
- this.audionode1 = LGAudio.getAudioContext().createGain();
- this.audionode1.gain.value = this.properties.gain1;
- this.audionode2 = LGAudio.getAudioContext().createGain();
- this.audionode2.gain.value = this.properties.gain2;
-
- this.audionode1.connect( this.audionode );
- this.audionode2.connect( this.audionode );
-
- this.addInput("in1","audio");
- this.addInput("in1 gain","number");
- this.addInput("in2","audio");
- this.addInput("in2 gain","number");
-
- this.addOutput("out","audio");
-}
-
-LGAudioMixer.prototype.getAudioNodeInInputSlot = function( slot )
-{
- if(slot == 0)
- return this.audionode1;
- else if(slot == 2)
- return this.audionode2;
-}
-
-LGAudioMixer.prototype.onPropertyChanged = function( name, value )
-{
- if( name == "gain1" )
- this.audionode1.gain.value = value;
- else if( name == "gain2" )
- this.audionode2.gain.value = value;
-}
-
-
-LGAudioMixer.prototype.onExecute = function()
-{
- if(!this.inputs || !this.inputs.length)
- return;
-
- for(var i = 1; i < this.inputs.length; ++i)
- {
- var input = this.inputs[i];
-
- if(input.link == null || input.type == "audio")
- continue;
-
- var v = this.getInputData(i);
- if(v === undefined)
- continue;
-
- if(i == 1)
- this.audionode1.gain.value = v;
- else if(i == 3)
- this.audionode2.gain.value = v;
- }
-}
-
-LGAudio.createAudioNodeWrapper( LGAudioMixer );
-
-LGAudioMixer.title = "Mixer";
-LGAudioMixer.desc = "Audio mixer";
-LiteGraph.registerNodeType("audio/mixer", LGAudioMixer);
-
-
-function LGAudioDelay()
-{
- //default
- this.properties = {
- delayTime: 0.5
- };
-
- this.audionode = LGAudio.getAudioContext().createDelay( 10 );
- this.audionode.delayTime.value = this.properties.delayTime;
- this.addInput("in","audio");
- this.addInput("time","number");
- this.addOutput("out","audio");
-}
-
-LGAudio.createAudioNodeWrapper( LGAudioDelay );
-
-LGAudioDelay.prototype.onExecute = function()
-{
- var v = this.getInputData(1);
- if(v !== undefined )
- this.audionode.delayTime.value = v;
-}
-
-LGAudioDelay.title = "Delay";
-LGAudioDelay.desc = "Audio delay";
-LiteGraph.registerNodeType("audio/delay", LGAudioDelay);
-
-
-function LGAudioBiquadFilter()
-{
- //default
- this.properties = {
- frequency: 350,
- detune: 0,
- Q: 1
- };
- this.addProperty("type","lowpass","enum",{values:["lowpass","highpass","bandpass","lowshelf","highshelf","peaking","notch","allpass"]});
-
- //create node
- this.audionode = LGAudio.getAudioContext().createBiquadFilter();
-
- //slots
- this.addInput("in","audio");
- this.addOutput("out","audio");
-}
-
-LGAudioBiquadFilter.prototype.onExecute = function()
-{
- if(!this.inputs || !this.inputs.length)
- return;
-
- for(var i = 1; i < this.inputs.length; ++i)
- {
- var input = this.inputs[i];
- if(input.link == null)
- continue;
- var v = this.getInputData(i);
- if(v !== undefined)
- this.audionode[ input.name ].value = v;
- }
-}
-
-LGAudioBiquadFilter.prototype.onGetInputs = function()
-{
- return [["frequency","number"],["detune","number"],["Q","number"]];
-}
-
-LGAudio.createAudioNodeWrapper( LGAudioBiquadFilter );
-
-LGAudioBiquadFilter.title = "BiquadFilter";
-LGAudioBiquadFilter.desc = "Audio filter";
-LiteGraph.registerNodeType("audio/biquadfilter", LGAudioBiquadFilter);
-
-
-
-
-function LGAudioOscillatorNode()
-{
- //default
- this.properties = {
- frequency: 440,
- detune: 0,
- type: "sine"
- };
- this.addProperty("type","sine","enum",{values:["sine","square","sawtooth","triangle","custom"]});
-
- //create node
- this.audionode = LGAudio.getAudioContext().createOscillator();
-
- //slots
- this.addOutput("out","audio");
-}
-
-LGAudioOscillatorNode.prototype.onStart = function()
-{
- if(!this.audionode.started)
- {
- this.audionode.started = true;
- this.audionode.start();
- }
-}
-
-LGAudioOscillatorNode.prototype.onStop = function()
-{
- if(this.audionode.started)
- {
- this.audionode.started = false;
- this.audionode.stop();
- }
-}
-
-LGAudioOscillatorNode.prototype.onPause = function()
-{
- this.onStop();
-}
-
-LGAudioOscillatorNode.prototype.onUnpause = function()
-{
- this.onStart();
-}
-
-LGAudioOscillatorNode.prototype.onExecute = function()
-{
- if(!this.inputs || !this.inputs.length)
- return;
-
- for(var i = 0; i < this.inputs.length; ++i)
- {
- var input = this.inputs[i];
- if(input.link == null)
- continue;
- var v = this.getInputData(i);
- if(v !== undefined)
- this.audionode[ input.name ].value = v;
- }
-}
-
-LGAudioOscillatorNode.prototype.onGetInputs = function()
-{
- return [["frequency","number"],["detune","number"],["type","string"]];
-}
-
-LGAudio.createAudioNodeWrapper( LGAudioOscillatorNode );
-
-LGAudioOscillatorNode.title = "Oscillator";
-LGAudioOscillatorNode.desc = "Oscillator";
-LiteGraph.registerNodeType("audio/oscillator", LGAudioOscillatorNode);
-
-
-//*****************************************************
-
-//EXTRA
-
-
-function LGAudioVisualization()
-{
- this.properties = {
- continuous: true,
- mark: -1
- };
-
- this.addInput("data","array");
- this.addInput("mark","number");
- this.size = [300,200];
- this._last_buffer = null;
-}
-
-LGAudioVisualization.prototype.onExecute = function()
-{
- this._last_buffer = this.getInputData(0);
- var v = this.getInputData(1);
- if(v !== undefined)
- this.properties.mark = v;
- this.setDirtyCanvas(true,false);
-}
-
-LGAudioVisualization.prototype.onDrawForeground = function(ctx)
-{
- if(!this._last_buffer)
- return;
-
- var buffer = this._last_buffer;
-
- //delta represents how many samples we advance per pixel
- var delta = buffer.length / this.size[0];
- var h = this.size[1];
-
- ctx.fillStyle = "black";
- ctx.fillRect(0,0,this.size[0],this.size[1]);
- ctx.strokeStyle = "white";
- ctx.beginPath();
- var x = 0;
-
- if(this.properties.continuous)
- {
- ctx.moveTo(x,h);
- for(var i = 0; i < buffer.length; i+= delta)
- {
- ctx.lineTo(x,h - (buffer[i|0]/255) * h);
- x++;
- }
- }
- else
- {
- for(var i = 0; i < buffer.length; i+= delta)
- {
- ctx.moveTo(x+0.5,h);
- ctx.lineTo(x+0.5,h - (buffer[i|0]/255) * h);
- x++;
- }
- }
- ctx.stroke();
-
- if(this.properties.mark >= 0)
- {
- var samplerate = LGAudio.getAudioContext().sampleRate;
- var binfreq = samplerate / buffer.length;
- var x = 2 * (this.properties.mark / binfreq) / delta;
- if(x >= this.size[0])
- x = this.size[0]-1;
- ctx.strokeStyle = "red";
- ctx.beginPath();
- ctx.moveTo(x,h);
- ctx.lineTo(x,0);
- ctx.stroke();
- }
-}
-
-LGAudioVisualization.title = "Visualization";
-LGAudioVisualization.desc = "Audio Visualization";
-LiteGraph.registerNodeType("audio/visualization", LGAudioVisualization);
-
-
-function LGAudioBandSignal()
-{
- //default
- this.properties = {
- band: 440,
- amplitude: 1
- };
-
- this.addInput("freqs","array");
- this.addOutput("signal","number");
-}
-
-LGAudioBandSignal.prototype.onExecute = function()
-{
- this._freqs = this.getInputData(0);
- if( !this._freqs )
- return;
-
- var band = this.properties.band;
- var v = this.getInputData(1);
- if(v !== undefined)
- band = v;
-
- var samplerate = LGAudio.getAudioContext().sampleRate;
- var binfreq = samplerate / this._freqs.length;
- var index = 2 * (band / binfreq);
- var v = 0;
- if( index < 0 )
- v = this._freqs[ 0 ];
- if( index >= this._freqs.length )
- v = this._freqs[ this._freqs.length - 1];
- else
- {
- var pos = index|0;
- var v0 = this._freqs[ pos ];
- var v1 = this._freqs[ pos+1 ];
- var f = index - pos;
- v = v0 * (1-f) + v1 * f;
- }
-
- this.setOutputData( 0, (v/255) * this.properties.amplitude );
-}
-
-LGAudioBandSignal.prototype.onGetInputs = function()
-{
- return [["band","number"]];
-}
-
-LGAudioBandSignal.title = "Signal";
-LGAudioBandSignal.desc = "extract the signal of some frequency";
-LiteGraph.registerNodeType("audio/signal", LGAudioBandSignal);
-
-
-function LGAudioScript()
-{
- if(!LGAudioScript.default_code)
- {
- var code = LGAudioScript.default_function.toString();
- var index = code.indexOf("{")+1;
- var index2 = code.lastIndexOf("}");
- LGAudioScript.default_code = code.substr(index, index2 - index);
- }
-
- //default
- this.properties = {
- code: LGAudioScript.default_code
- };
-
- //create node
- var ctx = LGAudio.getAudioContext();
- if(ctx.createScriptProcessor)
- this.audionode = ctx.createScriptProcessor(4096,1,1); //buffer size, input channels, output channels
- else
- {
- console.warn("ScriptProcessorNode deprecated");
- this.audionode = ctx.createGain(); //bypass audio
- }
-
- this.processCode();
- if(!LGAudioScript._bypass_function)
- LGAudioScript._bypass_function = this.audionode.onaudioprocess;
-
- //slots
- this.addInput("in","audio");
- this.addOutput("out","audio");
-}
-
-LGAudioScript.prototype.onAdded = function( graph )
-{
- if(graph.status == LGraph.STATUS_RUNNING)
- this.audionode.onaudioprocess = this._callback;
-}
-
-LGAudioScript["@code"] = { widget: "code" };
-
-LGAudioScript.prototype.onStart = function()
-{
- this.audionode.onaudioprocess = this._callback;
-}
-
-LGAudioScript.prototype.onStop = function()
-{
- this.audionode.onaudioprocess = LGAudioScript._bypass_function;
-}
-
-LGAudioScript.prototype.onPause = function()
-{
- this.audionode.onaudioprocess = LGAudioScript._bypass_function;
-}
-
-LGAudioScript.prototype.onUnpause = function()
-{
- this.audionode.onaudioprocess = this._callback;
-}
-
-LGAudioScript.prototype.onExecute = function()
-{
- //nothing! because we need an onExecute to receive onStart... fix that
-}
-
-LGAudioScript.prototype.onRemoved = function()
-{
- this.audionode.onaudioprocess = LGAudioScript._bypass_function;
-}
-
-LGAudioScript.prototype.processCode = function()
-{
- try
- {
- var func = new Function( "properties", this.properties.code );
- this._script = new func( this.properties );
- this._old_code = this.properties.code;
- this._callback = this._script.onaudioprocess;
- }
- catch (err)
- {
- console.error("Error in onaudioprocess code",err);
- this._callback = LGAudioScript._bypass_function;
- this.audionode.onaudioprocess = this._callback;
- }
-}
-
-LGAudioScript.prototype.onPropertyChanged = function( name, value )
-{
- if(name == "code")
- {
- this.properties.code = value;
- this.processCode();
- if(this.graph && this.graph.status == LGraph.STATUS_RUNNING)
- this.audionode.onaudioprocess = this._callback;
- }
-}
-
-LGAudioScript.default_function = function()
-{
-
-this.onaudioprocess = function(audioProcessingEvent) {
- // The input buffer is the song we loaded earlier
- var inputBuffer = audioProcessingEvent.inputBuffer;
-
- // The output buffer contains the samples that will be modified and played
- var outputBuffer = audioProcessingEvent.outputBuffer;
-
- // Loop through the output channels (in this case there is only one)
- for (var channel = 0; channel < outputBuffer.numberOfChannels; channel++) {
- var inputData = inputBuffer.getChannelData(channel);
- var outputData = outputBuffer.getChannelData(channel);
-
- // Loop through the 4096 samples
- for (var sample = 0; sample < inputBuffer.length; sample++) {
- // make output equal to the same as the input
- outputData[sample] = inputData[sample];
- }
- }
-}
-
-}
-
-LGAudio.createAudioNodeWrapper( LGAudioScript );
-
-LGAudioScript.title = "Script";
-LGAudioScript.desc = "apply script to signal";
-LiteGraph.registerNodeType("audio/script", LGAudioScript);
-
-
-function LGAudioDestination()
-{
- this.audionode = LGAudio.getAudioContext().destination;
- this.addInput("in","audio");
-}
-
-
-LGAudioDestination.title = "Destination";
-LGAudioDestination.desc = "Audio output";
-LiteGraph.registerNodeType("audio/destination", LGAudioDestination);
-
-
-
-
+(function( global )
+{
+var LiteGraph = global.LiteGraph;
+
+var LGAudio = {};
+global.LGAudio = LGAudio;
+
+LGAudio.getAudioContext = function()
+{
+ if(!this._audio_context)
+ {
+ window.AudioContext = window.AudioContext || window.webkitAudioContext;
+ if(!window.AudioContext)
+ {
+ console.error("AudioContext not supported by browser");
+ return null;
+ }
+ this._audio_context = new AudioContext();
+ this._audio_context.onmessage = function(msg) { console.log("msg",msg);};
+ this._audio_context.onended = function(msg) { console.log("ended",msg);};
+ this._audio_context.oncomplete = function(msg) { console.log("complete",msg);};
+ }
+
+ //in case it crashes
+ //if(this._audio_context.state == "suspended")
+ // this._audio_context.resume();
+ return this._audio_context;
+}
+
+LGAudio.connect = function( audionodeA, audionodeB )
+{
+ try
+ {
+ audionodeA.connect( audionodeB );
+ }
+ catch (err)
+ {
+ console.warn("LGraphAudio:",err);
+ }
+}
+
+LGAudio.disconnect = function( audionodeA, audionodeB )
+{
+ try
+ {
+ audionodeA.disconnect( audionodeB );
+ }
+ catch (err)
+ {
+ console.warn("LGraphAudio:",err);
+ }
+}
+
+LGAudio.changeAllAudiosConnections = function( node, connect )
+{
+ if(node.inputs)
+ {
+ for(var i = 0; i < node.inputs.length; ++i)
+ {
+ var input = node.inputs[i];
+ var link_info = node.graph.links[ input.link ];
+ if(!link_info)
+ continue;
+
+ var origin_node = node.graph.getNodeById( link_info.origin_id );
+ var origin_audionode = null;
+ if( origin_node.getAudioNodeInOutputSlot )
+ origin_audionode = origin_node.getAudioNodeInOutputSlot( link_info.origin_slot );
+ else
+ origin_audionode = origin_node.audionode;
+
+ var target_audionode = null;
+ if( node.getAudioNodeInInputSlot )
+ target_audionode = node.getAudioNodeInInputSlot( i );
+ else
+ target_audionode = node.audionode;
+
+ if(connect)
+ LGAudio.connect( origin_audionode, target_audionode );
+ else
+ LGAudio.disconnect( origin_audionode, target_audionode );
+ }
+ }
+
+ if(node.outputs)
+ {
+ for(var i = 0; i < node.outputs.length; ++i)
+ {
+ var output = node.outputs[i];
+ for(var j = 0; j < output.links.length; ++j)
+ {
+ var link_info = node.graph.links[ output.links[j] ];
+ if(!link_info)
+ continue;
+
+ var origin_audionode = null;
+ if( node.getAudioNodeInOutputSlot )
+ origin_audionode = node.getAudioNodeInOutputSlot( i );
+ else
+ origin_audionode = node.audionode;
+
+ var target_node = node.graph.getNodeById( link_info.target_id );
+ var target_audionode = null;
+ if( target_node.getAudioNodeInInputSlot )
+ target_audionode = target_node.getAudioNodeInInputSlot( link_info.target_slot );
+ else
+ target_audionode = target_node.audionode;
+
+ if(connect)
+ LGAudio.connect( origin_audionode, target_audionode );
+ else
+ LGAudio.disconnect( origin_audionode, target_audionode );
+ }
+ }
+ }
+}
+
+//used by many nodes
+LGAudio.onConnectionsChange = function( connection, slot, connected, link_info )
+{
+ //only process the outputs events
+ if(connection != LiteGraph.OUTPUT)
+ return;
+
+ var target_node = null;
+ if( link_info )
+ target_node = this.graph.getNodeById( link_info.target_id );
+
+ if( !target_node )
+ return;
+
+ //get origin audionode
+ var local_audionode = null;
+ if(this.getAudioNodeInOutputSlot)
+ local_audionode = this.getAudioNodeInOutputSlot( slot );
+ else
+ local_audionode = this.audionode;
+
+ //get target audionode
+ var target_audionode = null;
+ if(target_node.getAudioNodeInInputSlot)
+ target_audionode = target_node.getAudioNodeInInputSlot( link_info.target_slot );
+ else
+ target_audionode = target_node.audionode;
+
+ //do the connection/disconnection
+ if( connected )
+ LGAudio.connect( local_audionode, target_audionode );
+ else
+ LGAudio.disconnect( local_audionode, target_audionode );
+}
+
+//this function helps creating wrappers to existing classes
+LGAudio.createAudioNodeWrapper = function( class_object )
+{
+ var old_func = class_object.prototype.onPropertyChanged;
+
+ class_object.prototype.onPropertyChanged = function(name, value)
+ {
+ if(old_func)
+ old_func.call(this,name,value);
+
+ if(!this.audionode)
+ return;
+
+ if( this.audionode[ name ] === undefined )
+ return;
+
+ if( this.audionode[ name ].value !== undefined )
+ this.audionode[ name ].value = value;
+ else
+ this.audionode[ name ] = value;
+ }
+
+ class_object.prototype.onConnectionsChange = LGAudio.onConnectionsChange;
+}
+
+//contains the samples decoded of the loaded audios in AudioBuffer format
+LGAudio.cached_audios = {};
+
+LGAudio.loadSound = function( url, on_complete, on_error )
+{
+ if( LGAudio.cached_audios[ url ] && url.indexOf("blob:") == -1 )
+ {
+ if(on_complete)
+ on_complete( LGAudio.cached_audios[ url ] );
+ return;
+ }
+
+ if( LGAudio.onProcessAudioURL )
+ url = LGAudio.onProcessAudioURL( url );
+
+ //load new sample
+ var request = new XMLHttpRequest();
+ request.open('GET', url, true);
+ request.responseType = 'arraybuffer';
+
+ var context = LGAudio.getAudioContext();
+
+ // Decode asynchronously
+ request.onload = function() {
+ console.log("AudioSource loaded");
+ context.decodeAudioData( request.response, function( buffer ) {
+ console.log("AudioSource decoded");
+ LGAudio.cached_audios[ url ] = buffer;
+ if(on_complete)
+ on_complete( buffer );
+ }, onError);
+ }
+ request.send();
+
+ function onError(err)
+ {
+ console.log("Audio loading sample error:",err);
+ if(on_error)
+ on_error(err);
+ }
+
+ return request;
+}
+
+
+//****************************************************
+
+function LGAudioSource()
+{
+ this.properties = {
+ src: "",
+ gain: 0.5,
+ loop: true,
+ autoplay: true,
+ playbackRate: 1
+ };
+
+ this._loading_audio = false;
+ this._audiobuffer = null; //points to AudioBuffer with the audio samples decoded
+ this._audionodes = [];
+ this._last_sourcenode = null; //the last AudioBufferSourceNode (there could be more if there are several sounds playing)
+
+ this.addOutput( "out", "audio" );
+ this.addInput( "gain", "number" );
+
+ //init context
+ var context = LGAudio.getAudioContext();
+
+ //create gain node to control volume
+ this.audionode = context.createGain();
+ this.audionode.graphnode = this;
+ this.audionode.gain.value = this.properties.gain;
+
+ //debug
+ if(this.properties.src)
+ this.loadSound( this.properties.src );
+}
+
+LGAudioSource["@src"] = { widget: "resource" };
+LGAudioSource.supported_extensions = ["wav","ogg","mp3"];
+
+
+LGAudioSource.prototype.onAdded = function(graph)
+{
+ if(graph.status === LGraph.STATUS_RUNNING)
+ this.onStart();
+}
+
+LGAudioSource.prototype.onStart = function()
+{
+ if(!this._audiobuffer)
+ return;
+
+ if(this.properties.autoplay)
+ this.playBuffer( this._audiobuffer );
+}
+
+LGAudioSource.prototype.onStop = function()
+{
+ this.stopAllSounds();
+}
+
+LGAudioSource.prototype.onPause = function()
+{
+ this.pauseAllSounds();
+}
+
+LGAudioSource.prototype.onUnpause = function()
+{
+ this.unpauseAllSounds();
+ //this.onStart();
+}
+
+
+LGAudioSource.prototype.onRemoved = function()
+{
+ this.stopAllSounds();
+ if(this._dropped_url)
+ URL.revokeObjectURL( this._url );
+}
+
+LGAudioSource.prototype.stopAllSounds = function()
+{
+ //iterate and stop
+ for(var i = 0; i < this._audionodes.length; ++i )
+ {
+ if(this._audionodes[i].started)
+ {
+ this._audionodes[i].started = false;
+ this._audionodes[i].stop();
+ }
+ //this._audionodes[i].disconnect( this.audionode );
+ }
+ this._audionodes.length = 0;
+}
+
+LGAudioSource.prototype.pauseAllSounds = function()
+{
+ LGAudio.getAudioContext().suspend();
+}
+
+LGAudioSource.prototype.unpauseAllSounds = function()
+{
+ LGAudio.getAudioContext().resume();
+}
+
+LGAudioSource.prototype.onExecute = function()
+{
+ if(this.inputs)
+ for(var i = 0; i < this.inputs.length; ++i)
+ {
+ var input = this.inputs[i];
+ if(input.link == null)
+ continue;
+ var v = this.getInputData(i);
+ if( v === undefined )
+ continue;
+ if( input.name == "gain" )
+ this.audionode.gain.value = v;
+ else if( input.name == "playbackRate" )
+ {
+ this.properties.playbackRate = v;
+ for(var j = 0; j < this._audionodes.length; ++j)
+ this._audionodes[j].playbackRate.value = v;
+ }
+ }
+
+ if(this.outputs)
+ for(var i = 0; i < this.outputs.length; ++i)
+ {
+ var output = this.outputs[i];
+ if( output.name == "buffer" && this._audiobuffer )
+ this.setOutputData( i, this._audiobuffer );
+ }
+}
+
+LGAudioSource.prototype.onAction = function(event)
+{
+ if(this._audiobuffer)
+ {
+ if(event == "Play")
+ this.playBuffer(this._audiobuffer);
+ else if(event == "Stop")
+ this.stopAllSounds();
+ }
+}
+
+LGAudioSource.prototype.onPropertyChanged = function( name, value )
+{
+ if( name == "src" )
+ this.loadSound( value );
+ else if(name == "gain")
+ this.audionode.gain.value = value;
+ else if(name == "playbackRate")
+ {
+ for(var j = 0; j < this._audionodes.length; ++j)
+ this._audionodes[j].playbackRate.value = value;
+ }
+}
+
+LGAudioSource.prototype.playBuffer = function( buffer )
+{
+ var that = this;
+ var context = LGAudio.getAudioContext();
+
+ //create a new audionode (this is mandatory, AudioAPI doesnt like to reuse old ones)
+ var audionode = context.createBufferSource(); //create a AudioBufferSourceNode
+ this._last_sourcenode = audionode;
+ audionode.graphnode = this;
+ audionode.buffer = buffer;
+ audionode.loop = this.properties.loop;
+ audionode.playbackRate.value = this.properties.playbackRate;
+ this._audionodes.push( audionode );
+ audionode.connect( this.audionode ); //connect to gain
+ this._audionodes.push( audionode );
+
+ audionode.onended = function()
+ {
+ //console.log("ended!");
+ that.trigger("ended");
+ //remove
+ var index = that._audionodes.indexOf( audionode );
+ if(index != -1)
+ that._audionodes.splice(index,1);
+ }
+
+ if(!audionode.started)
+ {
+ audionode.started = true;
+ audionode.start();
+ }
+ return audionode;
+}
+
+LGAudioSource.prototype.loadSound = function( url )
+{
+ var that = this;
+
+ //kill previous load
+ if(this._request)
+ {
+ this._request.abort();
+ this._request = null;
+ }
+
+ this._audiobuffer = null; //points to the audiobuffer once the audio is loaded
+ this._loading_audio = false;
+
+ if(!url)
+ return;
+
+ this._request = LGAudio.loadSound( url, inner );
+
+ this._loading_audio = true;
+ this.boxcolor = "#AA4";
+
+ function inner( buffer )
+ {
+ this.boxcolor = LiteGraph.NODE_DEFAULT_BOXCOLOR;
+ that._audiobuffer = buffer;
+ that._loading_audio = false;
+ //if is playing, then play it
+ if(that.graph && that.graph.status === LGraph.STATUS_RUNNING)
+ that.onStart(); //this controls the autoplay already
+ }
+}
+
+//Helps connect/disconnect AudioNodes when new connections are made in the node
+LGAudioSource.prototype.onConnectionsChange = LGAudio.onConnectionsChange;
+
+LGAudioSource.prototype.onGetInputs = function()
+{
+ return [["playbackRate","number"],["Play",LiteGraph.ACTION],["Stop",LiteGraph.ACTION]];
+}
+
+LGAudioSource.prototype.onGetOutputs = function()
+{
+ return [["buffer","audiobuffer"],["ended",LiteGraph.EVENT]];
+}
+
+LGAudioSource.prototype.onDropFile = function(file)
+{
+ if(this._dropped_url)
+ URL.revokeObjectURL( this._dropped_url );
+ var url = URL.createObjectURL( file );
+ this.properties.src = url;
+ this.loadSound( url );
+ this._dropped_url = url;
+}
+
+
+LGAudioSource.title = "Source";
+LGAudioSource.desc = "Plays audio";
+LiteGraph.registerNodeType("audio/source", LGAudioSource);
+
+
+//*****************************************************
+
+function LGAudioAnalyser()
+{
+ this.properties = {
+ fftSize: 2048,
+ minDecibels: -100,
+ maxDecibels: -10,
+ smoothingTimeConstant: 0.5
+ };
+
+ var context = LGAudio.getAudioContext();
+
+ this.audionode = context.createAnalyser();
+ this.audionode.graphnode = this;
+ this.audionode.fftSize = this.properties.fftSize;
+ this.audionode.minDecibels = this.properties.minDecibels;
+ this.audionode.maxDecibels = this.properties.maxDecibels;
+ this.audionode.smoothingTimeConstant = this.properties.smoothingTimeConstant;
+
+ this.addInput("in","audio");
+ this.addOutput("freqs","array");
+ this.addOutput("samples","array");
+
+ this._freq_bin = null;
+ this._time_bin = null;
+}
+
+LGAudioAnalyser.prototype.onPropertyChanged = function(name, value)
+{
+ this.audionode[ name ] = value;
+}
+
+LGAudioAnalyser.prototype.onExecute = function()
+{
+ if(this.isOutputConnected(0))
+ {
+ //send FFT
+ var bufferLength = this.audionode.frequencyBinCount;
+ if( !this._freq_bin || this._freq_bin.length != bufferLength )
+ this._freq_bin = new Uint8Array( bufferLength );
+ this.audionode.getByteFrequencyData( this._freq_bin );
+ this.setOutputData(0,this._freq_bin);
+ }
+
+ //send analyzer
+ if(this.isOutputConnected(1))
+ {
+ //send Samples
+ var bufferLength = this.audionode.frequencyBinCount;
+ if( !this._time_bin || this._time_bin.length != bufferLength )
+ this._time_bin = new Uint8Array( bufferLength );
+ this.audionode.getByteTimeDomainData( this._time_bin );
+ this.setOutputData(1,this._time_bin);
+ }
+
+
+ //properties
+ for(var i = 1; i < this.inputs.length; ++i)
+ {
+ var input = this.inputs[i];
+ if(input.link == null)
+ continue;
+ var v = this.getInputData(i);
+ if (v !== undefined)
+ this.audionode[ input.name ].value = v;
+ }
+
+
+
+ //time domain
+ //this.audionode.getFloatTimeDomainData( dataArray );
+}
+
+LGAudioAnalyser.prototype.onGetInputs = function()
+{
+ return [["minDecibels","number"],["maxDecibels","number"],["smoothingTimeConstant","number"]];
+}
+
+LGAudioAnalyser.prototype.onGetOutputs = function()
+{
+ return [["freqs","array"],["samples","array"]];
+}
+
+
+LGAudioAnalyser.title = "Analyser";
+LGAudioAnalyser.desc = "Audio Analyser";
+LiteGraph.registerNodeType( "audio/analyser", LGAudioAnalyser );
+
+//*****************************************************
+
+function LGAudioGain()
+{
+ //default
+ this.properties = {
+ gain: 1
+ };
+
+ this.audionode = LGAudio.getAudioContext().createGain();
+ this.addInput("in","audio");
+ this.addInput("gain","number");
+ this.addOutput("out","audio");
+}
+
+LGAudioGain.prototype.onExecute = function()
+{
+ if(!this.inputs || !this.inputs.length)
+ return;
+
+ for(var i = 1; i < this.inputs.length; ++i)
+ {
+ var input = this.inputs[i];
+ var v = this.getInputData(i);
+ if(v !== undefined)
+ this.audionode[ input.name ].value = v;
+ }
+}
+
+LGAudio.createAudioNodeWrapper( LGAudioGain );
+
+LGAudioGain.title = "Gain";
+LGAudioGain.desc = "Audio gain";
+LiteGraph.registerNodeType("audio/gain", LGAudioGain);
+
+
+function LGAudioConvolver()
+{
+ //default
+ this.properties = {
+ impulse_src:"",
+ normalize: true
+ };
+
+ this.audionode = LGAudio.getAudioContext().createConvolver();
+ this.addInput("in","audio");
+ this.addOutput("out","audio");
+}
+
+LGAudio.createAudioNodeWrapper( LGAudioConvolver );
+
+LGAudioConvolver.prototype.onRemove = function()
+{
+ if(this._dropped_url)
+ URL.revokeObjectURL( this._dropped_url );
+}
+
+LGAudioConvolver.prototype.onPropertyChanged = function( name, value )
+{
+ if( name == "impulse_src" )
+ this.loadImpulse( value );
+ else if( name == "normalize" )
+ this.audionode.normalize = value;
+}
+
+LGAudioConvolver.prototype.onDropFile = function(file)
+{
+ if(this._dropped_url)
+ URL.revokeObjectURL( this._dropped_url );
+ this._dropped_url = URL.createObjectURL( file );
+ this.properties.impulse_src = this._dropped_url;
+ this.loadImpulse( this._dropped_url );
+}
+
+LGAudioConvolver.prototype.loadImpulse = function( url )
+{
+ var that = this;
+
+ //kill previous load
+ if(this._request)
+ {
+ this._request.abort();
+ this._request = null;
+ }
+
+ this._impulse_buffer = null;
+ this._loading_impulse = false;
+
+ if(!url)
+ return;
+
+ //load new sample
+ this._request = LGAudio.loadSound( url, inner );
+ this._loading_impulse = true;
+
+ // Decode asynchronously
+ function inner( buffer ) {
+ that._impulse_buffer = buffer;
+ that.audionode.buffer = buffer;
+ console.log("Impulse signal set");
+ that._loading_impulse = false;
+ }
+}
+
+LGAudioConvolver.title = "Convolver";
+LGAudioConvolver.desc = "Convolves the signal (used for reverb)";
+LiteGraph.registerNodeType("audio/convolver", LGAudioConvolver);
+
+
+function LGAudioDynamicsCompressor()
+{
+ //default
+ this.properties = {
+ threshold: -50,
+ knee: 40,
+ ratio: 12,
+ reduction: -20,
+ attack: 0,
+ release: 0.25
+ };
+
+ this.audionode = LGAudio.getAudioContext().createDynamicsCompressor();
+ this.addInput("in","audio");
+ this.addOutput("out","audio");
+}
+
+LGAudio.createAudioNodeWrapper( LGAudioDynamicsCompressor );
+
+LGAudioDynamicsCompressor.prototype.onExecute = function()
+{
+ if(!this.inputs || !this.inputs.length)
+ return;
+ for(var i = 1; i < this.inputs.length; ++i)
+ {
+ var input = this.inputs[i];
+ if(input.link == null)
+ continue;
+ var v = this.getInputData(i);
+ if(v !== undefined)
+ this.audionode[ input.name ].value = v;
+ }
+}
+
+LGAudioDynamicsCompressor.prototype.onGetInputs = function()
+{
+ return [["threshold","number"],["knee","number"],["ratio","number"],["reduction","number"],["attack","number"],["release","number"]];
+}
+
+LGAudioDynamicsCompressor.title = "DynamicsCompressor";
+LGAudioDynamicsCompressor.desc = "Dynamics Compressor";
+LiteGraph.registerNodeType("audio/dynamicsCompressor", LGAudioDynamicsCompressor);
+
+
+function LGAudioWaveShaper()
+{
+ //default
+ this.properties = {
+ };
+
+ this.audionode = LGAudio.getAudioContext().createWaveShaper();
+ this.addInput("in","audio");
+ this.addInput("shape","waveshape");
+ this.addOutput("out","audio");
+}
+
+LGAudioWaveShaper.prototype.onExecute = function()
+{
+ if(!this.inputs || !this.inputs.length)
+ return;
+ var v = this.getInputData(1);
+ if(v === undefined)
+ return;
+ this.audionode.curve = v;
+}
+
+LGAudioWaveShaper.prototype.setWaveShape = function(shape)
+{
+ this.audionode.curve = shape;
+}
+
+LGAudio.createAudioNodeWrapper( LGAudioWaveShaper );
+
+/* disabled till I dont find a way to do a wave shape
+LGAudioWaveShaper.title = "WaveShaper";
+LGAudioWaveShaper.desc = "Distortion using wave shape";
+LiteGraph.registerNodeType("audio/waveShaper", LGAudioWaveShaper);
+*/
+
+function LGAudioMixer()
+{
+ //default
+ this.properties = {
+ gain1: 0.5,
+ gain2: 0.5
+ };
+
+ this.audionode = LGAudio.getAudioContext().createGain();
+
+ this.audionode1 = LGAudio.getAudioContext().createGain();
+ this.audionode1.gain.value = this.properties.gain1;
+ this.audionode2 = LGAudio.getAudioContext().createGain();
+ this.audionode2.gain.value = this.properties.gain2;
+
+ this.audionode1.connect( this.audionode );
+ this.audionode2.connect( this.audionode );
+
+ this.addInput("in1","audio");
+ this.addInput("in1 gain","number");
+ this.addInput("in2","audio");
+ this.addInput("in2 gain","number");
+
+ this.addOutput("out","audio");
+}
+
+LGAudioMixer.prototype.getAudioNodeInInputSlot = function( slot )
+{
+ if(slot == 0)
+ return this.audionode1;
+ else if(slot == 2)
+ return this.audionode2;
+}
+
+LGAudioMixer.prototype.onPropertyChanged = function( name, value )
+{
+ if( name == "gain1" )
+ this.audionode1.gain.value = value;
+ else if( name == "gain2" )
+ this.audionode2.gain.value = value;
+}
+
+
+LGAudioMixer.prototype.onExecute = function()
+{
+ if(!this.inputs || !this.inputs.length)
+ return;
+
+ for(var i = 1; i < this.inputs.length; ++i)
+ {
+ var input = this.inputs[i];
+
+ if(input.link == null || input.type == "audio")
+ continue;
+
+ var v = this.getInputData(i);
+ if(v === undefined)
+ continue;
+
+ if(i == 1)
+ this.audionode1.gain.value = v;
+ else if(i == 3)
+ this.audionode2.gain.value = v;
+ }
+}
+
+LGAudio.createAudioNodeWrapper( LGAudioMixer );
+
+LGAudioMixer.title = "Mixer";
+LGAudioMixer.desc = "Audio mixer";
+LiteGraph.registerNodeType("audio/mixer", LGAudioMixer);
+
+
+function LGAudioDelay()
+{
+ //default
+ this.properties = {
+ delayTime: 0.5
+ };
+
+ this.audionode = LGAudio.getAudioContext().createDelay( 10 );
+ this.audionode.delayTime.value = this.properties.delayTime;
+ this.addInput("in","audio");
+ this.addInput("time","number");
+ this.addOutput("out","audio");
+}
+
+LGAudio.createAudioNodeWrapper( LGAudioDelay );
+
+LGAudioDelay.prototype.onExecute = function()
+{
+ var v = this.getInputData(1);
+ if(v !== undefined )
+ this.audionode.delayTime.value = v;
+}
+
+LGAudioDelay.title = "Delay";
+LGAudioDelay.desc = "Audio delay";
+LiteGraph.registerNodeType("audio/delay", LGAudioDelay);
+
+
+function LGAudioBiquadFilter()
+{
+ //default
+ this.properties = {
+ frequency: 350,
+ detune: 0,
+ Q: 1
+ };
+ this.addProperty("type","lowpass","enum",{values:["lowpass","highpass","bandpass","lowshelf","highshelf","peaking","notch","allpass"]});
+
+ //create node
+ this.audionode = LGAudio.getAudioContext().createBiquadFilter();
+
+ //slots
+ this.addInput("in","audio");
+ this.addOutput("out","audio");
+}
+
+LGAudioBiquadFilter.prototype.onExecute = function()
+{
+ if(!this.inputs || !this.inputs.length)
+ return;
+
+ for(var i = 1; i < this.inputs.length; ++i)
+ {
+ var input = this.inputs[i];
+ if(input.link == null)
+ continue;
+ var v = this.getInputData(i);
+ if(v !== undefined)
+ this.audionode[ input.name ].value = v;
+ }
+}
+
+LGAudioBiquadFilter.prototype.onGetInputs = function()
+{
+ return [["frequency","number"],["detune","number"],["Q","number"]];
+}
+
+LGAudio.createAudioNodeWrapper( LGAudioBiquadFilter );
+
+LGAudioBiquadFilter.title = "BiquadFilter";
+LGAudioBiquadFilter.desc = "Audio filter";
+LiteGraph.registerNodeType("audio/biquadfilter", LGAudioBiquadFilter);
+
+
+
+
+function LGAudioOscillatorNode()
+{
+ //default
+ this.properties = {
+ frequency: 440,
+ detune: 0,
+ type: "sine"
+ };
+ this.addProperty("type","sine","enum",{values:["sine","square","sawtooth","triangle","custom"]});
+
+ //create node
+ this.audionode = LGAudio.getAudioContext().createOscillator();
+
+ //slots
+ this.addOutput("out","audio");
+}
+
+LGAudioOscillatorNode.prototype.onStart = function()
+{
+ if(!this.audionode.started)
+ {
+ this.audionode.started = true;
+ this.audionode.start();
+ }
+}
+
+LGAudioOscillatorNode.prototype.onStop = function()
+{
+ if(this.audionode.started)
+ {
+ this.audionode.started = false;
+ this.audionode.stop();
+ }
+}
+
+LGAudioOscillatorNode.prototype.onPause = function()
+{
+ this.onStop();
+}
+
+LGAudioOscillatorNode.prototype.onUnpause = function()
+{
+ this.onStart();
+}
+
+LGAudioOscillatorNode.prototype.onExecute = function()
+{
+ if(!this.inputs || !this.inputs.length)
+ return;
+
+ for(var i = 0; i < this.inputs.length; ++i)
+ {
+ var input = this.inputs[i];
+ if(input.link == null)
+ continue;
+ var v = this.getInputData(i);
+ if(v !== undefined)
+ this.audionode[ input.name ].value = v;
+ }
+}
+
+LGAudioOscillatorNode.prototype.onGetInputs = function()
+{
+ return [["frequency","number"],["detune","number"],["type","string"]];
+}
+
+LGAudio.createAudioNodeWrapper( LGAudioOscillatorNode );
+
+LGAudioOscillatorNode.title = "Oscillator";
+LGAudioOscillatorNode.desc = "Oscillator";
+LiteGraph.registerNodeType("audio/oscillator", LGAudioOscillatorNode);
+
+
+//*****************************************************
+
+//EXTRA
+
+
+function LGAudioVisualization()
+{
+ this.properties = {
+ continuous: true,
+ mark: -1
+ };
+
+ this.addInput("data","array");
+ this.addInput("mark","number");
+ this.size = [300,200];
+ this._last_buffer = null;
+}
+
+LGAudioVisualization.prototype.onExecute = function()
+{
+ this._last_buffer = this.getInputData(0);
+ var v = this.getInputData(1);
+ if(v !== undefined)
+ this.properties.mark = v;
+ this.setDirtyCanvas(true,false);
+}
+
+LGAudioVisualization.prototype.onDrawForeground = function(ctx)
+{
+ if(!this._last_buffer)
+ return;
+
+ var buffer = this._last_buffer;
+
+ //delta represents how many samples we advance per pixel
+ var delta = buffer.length / this.size[0];
+ var h = this.size[1];
+
+ ctx.fillStyle = "black";
+ ctx.fillRect(0,0,this.size[0],this.size[1]);
+ ctx.strokeStyle = "white";
+ ctx.beginPath();
+ var x = 0;
+
+ if(this.properties.continuous)
+ {
+ ctx.moveTo(x,h);
+ for(var i = 0; i < buffer.length; i+= delta)
+ {
+ ctx.lineTo(x,h - (buffer[i|0]/255) * h);
+ x++;
+ }
+ }
+ else
+ {
+ for(var i = 0; i < buffer.length; i+= delta)
+ {
+ ctx.moveTo(x+0.5,h);
+ ctx.lineTo(x+0.5,h - (buffer[i|0]/255) * h);
+ x++;
+ }
+ }
+ ctx.stroke();
+
+ if(this.properties.mark >= 0)
+ {
+ var samplerate = LGAudio.getAudioContext().sampleRate;
+ var binfreq = samplerate / buffer.length;
+ var x = 2 * (this.properties.mark / binfreq) / delta;
+ if(x >= this.size[0])
+ x = this.size[0]-1;
+ ctx.strokeStyle = "red";
+ ctx.beginPath();
+ ctx.moveTo(x,h);
+ ctx.lineTo(x,0);
+ ctx.stroke();
+ }
+}
+
+LGAudioVisualization.title = "Visualization";
+LGAudioVisualization.desc = "Audio Visualization";
+LiteGraph.registerNodeType("audio/visualization", LGAudioVisualization);
+
+
+function LGAudioBandSignal()
+{
+ //default
+ this.properties = {
+ band: 440,
+ amplitude: 1
+ };
+
+ this.addInput("freqs","array");
+ this.addOutput("signal","number");
+}
+
+LGAudioBandSignal.prototype.onExecute = function()
+{
+ this._freqs = this.getInputData(0);
+ if( !this._freqs )
+ return;
+
+ var band = this.properties.band;
+ var v = this.getInputData(1);
+ if(v !== undefined)
+ band = v;
+
+ var samplerate = LGAudio.getAudioContext().sampleRate;
+ var binfreq = samplerate / this._freqs.length;
+ var index = 2 * (band / binfreq);
+ var v = 0;
+ if( index < 0 )
+ v = this._freqs[ 0 ];
+ if( index >= this._freqs.length )
+ v = this._freqs[ this._freqs.length - 1];
+ else
+ {
+ var pos = index|0;
+ var v0 = this._freqs[ pos ];
+ var v1 = this._freqs[ pos+1 ];
+ var f = index - pos;
+ v = v0 * (1-f) + v1 * f;
+ }
+
+ this.setOutputData( 0, (v/255) * this.properties.amplitude );
+}
+
+LGAudioBandSignal.prototype.onGetInputs = function()
+{
+ return [["band","number"]];
+}
+
+LGAudioBandSignal.title = "Signal";
+LGAudioBandSignal.desc = "extract the signal of some frequency";
+LiteGraph.registerNodeType("audio/signal", LGAudioBandSignal);
+
+
+function LGAudioScript()
+{
+ if(!LGAudioScript.default_code)
+ {
+ var code = LGAudioScript.default_function.toString();
+ var index = code.indexOf("{")+1;
+ var index2 = code.lastIndexOf("}");
+ LGAudioScript.default_code = code.substr(index, index2 - index);
+ }
+
+ //default
+ this.properties = {
+ code: LGAudioScript.default_code
+ };
+
+ //create node
+ var ctx = LGAudio.getAudioContext();
+ if(ctx.createScriptProcessor)
+ this.audionode = ctx.createScriptProcessor(4096,1,1); //buffer size, input channels, output channels
+ else
+ {
+ console.warn("ScriptProcessorNode deprecated");
+ this.audionode = ctx.createGain(); //bypass audio
+ }
+
+ this.processCode();
+ if(!LGAudioScript._bypass_function)
+ LGAudioScript._bypass_function = this.audionode.onaudioprocess;
+
+ //slots
+ this.addInput("in","audio");
+ this.addOutput("out","audio");
+}
+
+LGAudioScript.prototype.onAdded = function( graph )
+{
+ if(graph.status == LGraph.STATUS_RUNNING)
+ this.audionode.onaudioprocess = this._callback;
+}
+
+LGAudioScript["@code"] = { widget: "code" };
+
+LGAudioScript.prototype.onStart = function()
+{
+ this.audionode.onaudioprocess = this._callback;
+}
+
+LGAudioScript.prototype.onStop = function()
+{
+ this.audionode.onaudioprocess = LGAudioScript._bypass_function;
+}
+
+LGAudioScript.prototype.onPause = function()
+{
+ this.audionode.onaudioprocess = LGAudioScript._bypass_function;
+}
+
+LGAudioScript.prototype.onUnpause = function()
+{
+ this.audionode.onaudioprocess = this._callback;
+}
+
+LGAudioScript.prototype.onExecute = function()
+{
+ //nothing! because we need an onExecute to receive onStart... fix that
+}
+
+LGAudioScript.prototype.onRemoved = function()
+{
+ this.audionode.onaudioprocess = LGAudioScript._bypass_function;
+}
+
+LGAudioScript.prototype.processCode = function()
+{
+ try
+ {
+ var func = new Function( "properties", this.properties.code );
+ this._script = new func( this.properties );
+ this._old_code = this.properties.code;
+ this._callback = this._script.onaudioprocess;
+ }
+ catch (err)
+ {
+ console.error("Error in onaudioprocess code",err);
+ this._callback = LGAudioScript._bypass_function;
+ this.audionode.onaudioprocess = this._callback;
+ }
+}
+
+LGAudioScript.prototype.onPropertyChanged = function( name, value )
+{
+ if(name == "code")
+ {
+ this.properties.code = value;
+ this.processCode();
+ if(this.graph && this.graph.status == LGraph.STATUS_RUNNING)
+ this.audionode.onaudioprocess = this._callback;
+ }
+}
+
+LGAudioScript.default_function = function()
+{
+
+this.onaudioprocess = function(audioProcessingEvent) {
+ // The input buffer is the song we loaded earlier
+ var inputBuffer = audioProcessingEvent.inputBuffer;
+
+ // The output buffer contains the samples that will be modified and played
+ var outputBuffer = audioProcessingEvent.outputBuffer;
+
+ // Loop through the output channels (in this case there is only one)
+ for (var channel = 0; channel < outputBuffer.numberOfChannels; channel++) {
+ var inputData = inputBuffer.getChannelData(channel);
+ var outputData = outputBuffer.getChannelData(channel);
+
+ // Loop through the 4096 samples
+ for (var sample = 0; sample < inputBuffer.length; sample++) {
+ // make output equal to the same as the input
+ outputData[sample] = inputData[sample];
+ }
+ }
+}
+
+}
+
+LGAudio.createAudioNodeWrapper( LGAudioScript );
+
+LGAudioScript.title = "Script";
+LGAudioScript.desc = "apply script to signal";
+LiteGraph.registerNodeType("audio/script", LGAudioScript);
+
+
+function LGAudioDestination()
+{
+ this.audionode = LGAudio.getAudioContext().destination;
+ this.addInput("in","audio");
+}
+
+
+LGAudioDestination.title = "Destination";
+LGAudioDestination.desc = "Audio output";
+LiteGraph.registerNodeType("audio/destination", LGAudioDestination);
+
+
+
+
})( this );
-//event related nodes
-(function(global){
-var LiteGraph = global.LiteGraph;
-
-function LGWebSocket()
-{
- this.size = [60,20];
- this.addInput("send", LiteGraph.ACTION);
- this.addOutput("received", LiteGraph.EVENT);
- this.addInput("in", 0 );
- this.addOutput("out", 0 );
- this.properties = {
- url: "",
- room: "lgraph" //allows to filter messages
- };
- this._ws = null;
- this._last_data = [];
-}
-
-LGWebSocket.title = "WebSocket";
-LGWebSocket.desc = "Send data through a websocket";
-
-LGWebSocket.prototype.onPropertyChanged = function(name,value)
-{
- if(name == "url")
- this.createSocket();
-}
-
-LGWebSocket.prototype.onExecute = function()
-{
- if(!this._ws && this.properties.url)
- this.createSocket();
-
- if(!this._ws || this._ws.readyState != WebSocket.OPEN )
- return;
-
- var room = this.properties.room;
-
- for(var i = 1; i < this.inputs.length; ++i)
- {
- var data = this.getInputData(i);
- if(data != null)
- {
- var json;
- try
- {
- json = JSON.stringify({ type: 0, room: room, channel: i, data: data });
- }
- catch (err)
- {
- continue;
- }
- this._ws.send( json );
- }
- }
-
- for(var i = 1; i < this.outputs.length; ++i)
- this.setOutputData( i, this._last_data[i] );
-}
-
-LGWebSocket.prototype.createSocket = function()
-{
- var that = this;
- var url = this.properties.url;
- if( url.substr(0,2) != "ws" )
- url = "ws://" + url;
- this._ws = new WebSocket( url );
- this._ws.onopen = function()
- {
- console.log("ready");
- that.boxcolor = "#8E8";
- }
- this._ws.onmessage = function(e)
- {
- var data = JSON.parse( e.data );
- if( data.room && data.room != this.properties.room )
- return;
- if( e.data.type == 1 )
- that.triggerSlot( 0, data );
- else
- that._last_data[ e.data.channel || 0 ] = data.data;
- }
- this._ws.onerror = function(e)
- {
- console.log("couldnt connect to websocket");
- that.boxcolor = "#E88";
- }
- this._ws.onclose = function(e)
- {
- console.log("connection closed");
- that.boxcolor = "#000";
- }
-}
-
-LGWebSocket.prototype.send = function(data)
-{
- if(!this._ws || this._ws.readyState != WebSocket.OPEN )
- return;
- this._ws.send( JSON.stringify({ type:1, msg: data }) );
-}
-
-LGWebSocket.prototype.onAction = function( action, param )
-{
- if(!this._ws || this._ws.readyState != WebSocket.OPEN )
- return;
- this._ws.send( { type: 1, room: this.properties.room, action: action, data: param } );
-}
-
-LGWebSocket.prototype.onGetInputs = function()
-{
- return [["in",0]];
-}
-
-LGWebSocket.prototype.onGetOutputs = function()
-{
- return [["out",0]];
-}
-
-LiteGraph.registerNodeType("network/websocket", LGWebSocket );
-
-
-//It is like a websocket but using the SillyServer.js server that bounces packets back to all clients connected:
-//For more information: https://github.com/jagenjo/SillyServer.js
-
-function LGSillyClient()
-{
- this.size = [60,20];
- this.addInput("send", LiteGraph.ACTION);
- this.addOutput("received", LiteGraph.EVENT);
- this.addInput("in", 0 );
- this.addOutput("out", 0 );
- this.properties = {
- url: "tamats.com:55000",
- room: "lgraph",
- save_bandwidth: true
- };
-
- this._server = null;
- this.createSocket();
- this._last_input_data = [];
- this._last_output_data = [];
-}
-
-LGSillyClient.title = "SillyClient";
-LGSillyClient.desc = "Connects to SillyServer to broadcast messages";
-
-LGSillyClient.prototype.onPropertyChanged = function(name,value)
-{
- var final_url = (this.properties.url + "/" + this.properties.room);
- if(this._server && this._final_url != final_url )
- {
- this._server.connect( this.properties.url, this.properties.room );
- this._final_url = final_url;
- }
-}
-
-LGSillyClient.prototype.onExecute = function()
-{
- if(!this._server || !this._server.is_connected)
- return;
-
- var save_bandwidth = this.properties.save_bandwidth;
-
- for(var i = 1; i < this.inputs.length; ++i)
- {
- var data = this.getInputData(i);
- if(data != null)
- {
- if( save_bandwidth && this._last_input_data[i] == data )
- continue;
- this._server.sendMessage( { type: 0, channel: i, data: data } );
- this._last_input_data[i] = data;
- }
- }
-
- for(var i = 1; i < this.outputs.length; ++i)
- this.setOutputData( i, this._last_output_data[i] );
-}
-
-LGSillyClient.prototype.createSocket = function()
-{
- var that = this;
- if(typeof(SillyClient) == "undefined")
- {
- if(!this._error)
- console.error("SillyClient node cannot be used, you must include SillyServer.js");
- this._error = true;
- return;
- }
-
- this._server = new SillyClient();
- this._server.on_ready = function()
- {
- console.log("ready");
- that.boxcolor = "#8E8";
- }
- this._server.on_message = function(id,msg)
- {
- var data = null;
- try
- {
- data = JSON.parse( msg );
- }
- catch (err)
- {
- return;
- }
-
- if(data.type == 1)
- that.triggerSlot( 0, data );
- else
- that._last_output_data[ data.channel || 0 ] = data.data;
- }
- this._server.on_error = function(e)
- {
- console.log("couldnt connect to websocket");
- that.boxcolor = "#E88";
- }
- this._server.on_close = function(e)
- {
- console.log("connection closed");
- that.boxcolor = "#000";
- }
-
- if(this.properties.url && this.properties.room)
- {
- this._server.connect( this.properties.url, this.properties.room );
- this._final_url = (this.properties.url + "/" + this.properties.room);
- }
-}
-
-LGSillyClient.prototype.send = function(data)
-{
- if(!this._server || !this._server.is_connected)
- return;
- this._server.sendMessage( { type:1, data: data } );
-}
-
-LGSillyClient.prototype.onAction = function( action, param )
-{
- if(!this._server || !this._server.is_connected)
- return;
- this._server.sendMessage( { type: 1, action: action, data: param } );
-}
-
-LGSillyClient.prototype.onGetInputs = function()
-{
- return [["in",0]];
-}
-
-LGSillyClient.prototype.onGetOutputs = function()
-{
- return [["out",0]];
-}
-
-LiteGraph.registerNodeType("network/sillyclient", LGSillyClient );
-
-
+//event related nodes
+(function(global){
+var LiteGraph = global.LiteGraph;
+
+function LGWebSocket()
+{
+ this.size = [60,20];
+ this.addInput("send", LiteGraph.ACTION);
+ this.addOutput("received", LiteGraph.EVENT);
+ this.addInput("in", 0 );
+ this.addOutput("out", 0 );
+ this.properties = {
+ url: "",
+ room: "lgraph" //allows to filter messages
+ };
+ this._ws = null;
+ this._last_data = [];
+}
+
+LGWebSocket.title = "WebSocket";
+LGWebSocket.desc = "Send data through a websocket";
+
+LGWebSocket.prototype.onPropertyChanged = function(name,value)
+{
+ if(name == "url")
+ this.createSocket();
+}
+
+LGWebSocket.prototype.onExecute = function()
+{
+ if(!this._ws && this.properties.url)
+ this.createSocket();
+
+ if(!this._ws || this._ws.readyState != WebSocket.OPEN )
+ return;
+
+ var room = this.properties.room;
+
+ for(var i = 1; i < this.inputs.length; ++i)
+ {
+ var data = this.getInputData(i);
+ if(data != null)
+ {
+ var json;
+ try
+ {
+ json = JSON.stringify({ type: 0, room: room, channel: i, data: data });
+ }
+ catch (err)
+ {
+ continue;
+ }
+ this._ws.send( json );
+ }
+ }
+
+ for(var i = 1; i < this.outputs.length; ++i)
+ this.setOutputData( i, this._last_data[i] );
+}
+
+LGWebSocket.prototype.createSocket = function()
+{
+ var that = this;
+ var url = this.properties.url;
+ if( url.substr(0,2) != "ws" )
+ url = "ws://" + url;
+ this._ws = new WebSocket( url );
+ this._ws.onopen = function()
+ {
+ console.log("ready");
+ that.boxcolor = "#8E8";
+ }
+ this._ws.onmessage = function(e)
+ {
+ var data = JSON.parse( e.data );
+ if( data.room && data.room != this.properties.room )
+ return;
+ if( e.data.type == 1 )
+ that.triggerSlot( 0, data );
+ else
+ that._last_data[ e.data.channel || 0 ] = data.data;
+ }
+ this._ws.onerror = function(e)
+ {
+ console.log("couldnt connect to websocket");
+ that.boxcolor = "#E88";
+ }
+ this._ws.onclose = function(e)
+ {
+ console.log("connection closed");
+ that.boxcolor = "#000";
+ }
+}
+
+LGWebSocket.prototype.send = function(data)
+{
+ if(!this._ws || this._ws.readyState != WebSocket.OPEN )
+ return;
+ this._ws.send( JSON.stringify({ type:1, msg: data }) );
+}
+
+LGWebSocket.prototype.onAction = function( action, param )
+{
+ if(!this._ws || this._ws.readyState != WebSocket.OPEN )
+ return;
+ this._ws.send( { type: 1, room: this.properties.room, action: action, data: param } );
+}
+
+LGWebSocket.prototype.onGetInputs = function()
+{
+ return [["in",0]];
+}
+
+LGWebSocket.prototype.onGetOutputs = function()
+{
+ return [["out",0]];
+}
+
+LiteGraph.registerNodeType("network/websocket", LGWebSocket );
+
+
+//It is like a websocket but using the SillyServer.js server that bounces packets back to all clients connected:
+//For more information: https://github.com/jagenjo/SillyServer.js
+
+function LGSillyClient()
+{
+ this.size = [60,20];
+ this.addInput("send", LiteGraph.ACTION);
+ this.addOutput("received", LiteGraph.EVENT);
+ this.addInput("in", 0 );
+ this.addOutput("out", 0 );
+ this.properties = {
+ url: "tamats.com:55000",
+ room: "lgraph",
+ save_bandwidth: true
+ };
+
+ this._server = null;
+ this.createSocket();
+ this._last_input_data = [];
+ this._last_output_data = [];
+}
+
+LGSillyClient.title = "SillyClient";
+LGSillyClient.desc = "Connects to SillyServer to broadcast messages";
+
+LGSillyClient.prototype.onPropertyChanged = function(name,value)
+{
+ var final_url = (this.properties.url + "/" + this.properties.room);
+ if(this._server && this._final_url != final_url )
+ {
+ this._server.connect( this.properties.url, this.properties.room );
+ this._final_url = final_url;
+ }
+}
+
+LGSillyClient.prototype.onExecute = function()
+{
+ if(!this._server || !this._server.is_connected)
+ return;
+
+ var save_bandwidth = this.properties.save_bandwidth;
+
+ for(var i = 1; i < this.inputs.length; ++i)
+ {
+ var data = this.getInputData(i);
+ if(data != null)
+ {
+ if( save_bandwidth && this._last_input_data[i] == data )
+ continue;
+ this._server.sendMessage( { type: 0, channel: i, data: data } );
+ this._last_input_data[i] = data;
+ }
+ }
+
+ for(var i = 1; i < this.outputs.length; ++i)
+ this.setOutputData( i, this._last_output_data[i] );
+}
+
+LGSillyClient.prototype.createSocket = function()
+{
+ var that = this;
+ if(typeof(SillyClient) == "undefined")
+ {
+ if(!this._error)
+ console.error("SillyClient node cannot be used, you must include SillyServer.js");
+ this._error = true;
+ return;
+ }
+
+ this._server = new SillyClient();
+ this._server.on_ready = function()
+ {
+ console.log("ready");
+ that.boxcolor = "#8E8";
+ }
+ this._server.on_message = function(id,msg)
+ {
+ var data = null;
+ try
+ {
+ data = JSON.parse( msg );
+ }
+ catch (err)
+ {
+ return;
+ }
+
+ if(data.type == 1)
+ that.triggerSlot( 0, data );
+ else
+ that._last_output_data[ data.channel || 0 ] = data.data;
+ }
+ this._server.on_error = function(e)
+ {
+ console.log("couldnt connect to websocket");
+ that.boxcolor = "#E88";
+ }
+ this._server.on_close = function(e)
+ {
+ console.log("connection closed");
+ that.boxcolor = "#000";
+ }
+
+ if(this.properties.url && this.properties.room)
+ {
+ this._server.connect( this.properties.url, this.properties.room );
+ this._final_url = (this.properties.url + "/" + this.properties.room);
+ }
+}
+
+LGSillyClient.prototype.send = function(data)
+{
+ if(!this._server || !this._server.is_connected)
+ return;
+ this._server.sendMessage( { type:1, data: data } );
+}
+
+LGSillyClient.prototype.onAction = function( action, param )
+{
+ if(!this._server || !this._server.is_connected)
+ return;
+ this._server.sendMessage( { type: 1, action: action, data: param } );
+}
+
+LGSillyClient.prototype.onGetInputs = function()
+{
+ return [["in",0]];
+}
+
+LGSillyClient.prototype.onGetOutputs = function()
+{
+ return [["out",0]];
+}
+
+LiteGraph.registerNodeType("network/sillyclient", LGSillyClient );
+
+
})(this);
diff --git a/build/litegraph.min.js b/build/litegraph.min.js
index 2e409b54d..26f54b391 100755
--- a/build/litegraph.min.js
+++ b/build/litegraph.min.js
@@ -1,3 +1,8241 @@
+var $jscomp = $jscomp || {};
+$jscomp.scope = {};
+$jscomp.ASSUME_ES5 = !1;
+$jscomp.ASSUME_NO_NATIVE_MAP = !1;
+$jscomp.ASSUME_NO_NATIVE_SET = !1;
+$jscomp.defineProperty = $jscomp.ASSUME_ES5 || "function" == typeof Object.defineProperties ? Object.defineProperty : function(u, f, h) {
+ u != Array.prototype && u != Object.prototype && (u[f] = h.value);
+};
+$jscomp.getGlobal = function(u) {
+ return "undefined" != typeof window && window === u ? u : "undefined" != typeof global && null != global ? global : u;
+};
+$jscomp.global = $jscomp.getGlobal(this);
+$jscomp.polyfill = function(u, f, h, n) {
+ if (f) {
+ h = $jscomp.global;
+ u = u.split(".");
+ for (n = 0; n < u.length - 1; n++) {
+ var d = u[n];
+ d in h || (h[d] = {});
+ h = h[d];
+ }
+ u = u[u.length - 1];
+ n = h[u];
+ f = f(n);
+ f != n && null != f && $jscomp.defineProperty(h, u, {configurable:!0, writable:!0, value:f});
+ }
+};
+$jscomp.polyfill("Array.prototype.fill", function(u) {
+ return u ? u : function(f, h, n) {
+ var d = this.length || 0;
+ 0 > h && (h = Math.max(0, d + h));
+ if (null == n || n > d) {
+ n = d;
+ }
+ n = Number(n);
+ 0 > n && (n = Math.max(0, d + n));
+ for (h = Number(h || 0); h < n; h++) {
+ this[h] = f;
+ }
+ return this;
+ };
+}, "es6", "es3");
+$jscomp.SYMBOL_PREFIX = "jscomp_symbol_";
+$jscomp.initSymbol = function() {
+ $jscomp.initSymbol = function() {
+ };
+ $jscomp.global.Symbol || ($jscomp.global.Symbol = $jscomp.Symbol);
+};
+$jscomp.Symbol = function() {
+ var u = 0;
+ return function(f) {
+ return $jscomp.SYMBOL_PREFIX + (f || "") + u++;
+ };
+}();
+$jscomp.initSymbolIterator = function() {
+ $jscomp.initSymbol();
+ var u = $jscomp.global.Symbol.iterator;
+ u || (u = $jscomp.global.Symbol.iterator = $jscomp.global.Symbol("iterator"));
+ "function" != typeof Array.prototype[u] && $jscomp.defineProperty(Array.prototype, u, {configurable:!0, writable:!0, value:function() {
+ return $jscomp.arrayIterator(this);
+ }});
+ $jscomp.initSymbolIterator = function() {
+ };
+};
+$jscomp.arrayIterator = function(u) {
+ var f = 0;
+ return $jscomp.iteratorPrototype(function() {
+ return f < u.length ? {done:!1, value:u[f++]} : {done:!0};
+ });
+};
+$jscomp.iteratorPrototype = function(u) {
+ $jscomp.initSymbolIterator();
+ u = {next:u};
+ u[$jscomp.global.Symbol.iterator] = function() {
+ return this;
+ };
+ return u;
+};
+$jscomp.iteratorFromArray = function(u, f) {
+ $jscomp.initSymbolIterator();
+ u instanceof String && (u += "");
+ var h = 0, n = {next:function() {
+ if (h < u.length) {
+ var d = h++;
+ return {value:f(d, u[d]), done:!1};
+ }
+ n.next = function() {
+ return {done:!0, value:void 0};
+ };
+ return n.next();
+ }};
+ n[Symbol.iterator] = function() {
+ return n;
+ };
+ return n;
+};
+$jscomp.polyfill("Array.prototype.values", function(u) {
+ return u ? u : function() {
+ return $jscomp.iteratorFromArray(this, function(f, h) {
+ return h;
+ });
+ };
+}, "es8", "es3");
+$jscomp.polyfill("Array.prototype.keys", function(u) {
+ return u ? u : function() {
+ return $jscomp.iteratorFromArray(this, function(f) {
+ return f;
+ });
+ };
+}, "es6", "es3");
+(function(u) {
+ function f(a) {
+ k.debug && console.log("Graph created");
+ this.list_of_graphcanvas = null;
+ this.clear();
+ a && this.configure(a);
+ }
+ function h(a) {
+ this._ctor(a);
+ }
+ function n(a) {
+ this._ctor(a);
+ }
+ function d(a, b, e) {
+ e = e || {};
+ this.background_image = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQBJREFUeNrs1rEKwjAUhlETUkj3vP9rdmr1Ysammk2w5wdxuLgcMHyptfawuZX4pJSWZTnfnu/lnIe/jNNxHHGNn//HNbbv+4dr6V+11uF527arU7+u63qfa/bnmh8sWLBgwYJlqRf8MEptXPBXJXa37BSl3ixYsGDBMliwFLyCV/DeLIMFCxYsWLBMwSt4Be/NggXLYMGCBUvBK3iNruC9WbBgwYJlsGApeAWv4L1ZBgsWLFiwYJmCV/AK3psFC5bBggULloJX8BpdwXuzYMGCBctgwVLwCl7Be7MMFixYsGDBsu8FH1FaSmExVfAxBa/gvVmwYMGCZbBg/W4vAQYA5tRF9QYlv/QAAAAASUVORK5CYII=";
+ a && a.constructor === String && (a = document.querySelector(a));
+ this.max_zoom = 10;
+ this.min_zoom = 0.1;
+ this.zoom_modify_alpha = !0;
+ this.title_text_font = "bold " + k.NODE_TEXT_SIZE + "px Arial";
+ this.inner_text_font = "normal " + k.NODE_SUBTEXT_SIZE + "px Arial";
+ this.node_title_color = k.NODE_TITLE_COLOR;
+ this.default_link_color = k.LINK_COLOR;
+ this.default_connection_color = {input_off:"#AAB", input_on:"#7F7", output_off:"#AAB", output_on:"#7F7"};
+ this.highquality_render = !0;
+ this.use_gradients = !1;
+ this.editor_alpha = 1;
+ this.pause_rendering = !1;
+ this.render_only_selected = this.clear_background = this.render_shadows = !0;
+ this.live_mode = !1;
+ this.allow_searchbox = this.allow_interaction = this.allow_dragnodes = this.allow_dragcanvas = this.show_info = !0;
+ this.drag_mode = !1;
+ this.filter = this.dragging_rectangle = null;
+ this.always_render_background = !1;
+ this.render_canvas_border = !0;
+ this.render_connections_shadows = !1;
+ this.render_connection_arrows = this.render_curved_connections = this.render_connections_border = !0;
+ this.render_execution_order = !1;
+ this.canvas_mouse = [0, 0];
+ this.onSearchBoxSelection = this.onSearchBox = null;
+ this.connections_width = 3;
+ this.round_radius = 8;
+ this.node_widget = this.current_node = null;
+ this.last_mouse_position = [0, 0];
+ b && b.attachCanvas(this);
+ this.setCanvas(a);
+ this.clear();
+ e.skip_render || this.startRendering();
+ this.autoresize = e.autoresize;
+ }
+ function q(a, b) {
+ return Math.sqrt((b[0] - a[0]) * (b[0] - a[0]) + (b[1] - a[1]) * (b[1] - a[1]));
+ }
+ function t(a, b, e, c, g, l) {
+ return e < a && e + g > a && c < b && c + l > b ? !0 : !1;
+ }
+ function v(a, b) {
+ var e = a[0] + a[2], c = a[1] + a[3], g = b[1] + b[3];
+ return a[0] > b[0] + b[2] || a[1] > g || e < b[0] || c < b[1] ? !1 : !0;
+ }
+ function w(a, b) {
+ function e(a) {
+ var b = parseInt(g.style.top);
+ g.style.top = (b + 0.1 * a.deltaY).toFixed() + "px";
+ a.preventDefault();
+ return !0;
+ }
+ this.options = b = b || {};
+ var c = this;
+ b.parentMenu && (b.parentMenu.constructor !== this.constructor ? (console.error("parentMenu must be of class ContextMenu, ignoring it"), b.parentMenu = null) : (this.parentMenu = b.parentMenu, this.parentMenu.lock = !0, this.parentMenu.current_submenu = this));
+ b.event && b.event.constructor !== MouseEvent && b.event.constructor !== CustomEvent && (console.error("Event passed to ContextMenu is not of type MouseEvent or CustomEvent. Ignoring it."), b.event = null);
+ var g = document.createElement("div");
+ g.className = "litegraph litecontextmenu litemenubar-panel";
+ g.style.minWidth = 100;
+ g.style.minHeight = 100;
+ g.style.pointerEvents = "none";
+ setTimeout(function() {
+ g.style.pointerEvents = "auto";
+ }, 100);
+ g.addEventListener("mouseup", function(a) {
+ a.preventDefault();
+ return !0;
+ }, !0);
+ g.addEventListener("contextmenu", function(a) {
+ if (2 != a.button) {
+ return !1;
+ }
+ a.preventDefault();
+ return !1;
+ }, !0);
+ g.addEventListener("mousedown", function(a) {
+ if (2 == a.button) {
+ return c.close(), a.preventDefault(), !0;
+ }
+ }, !0);
+ g.addEventListener("wheel", e, !0);
+ g.addEventListener("mousewheel", e, !0);
+ this.root = g;
+ if (b.title) {
+ var l = document.createElement("div");
+ l.className = "litemenu-title";
+ l.innerHTML = b.title;
+ g.appendChild(l);
+ }
+ l = 0;
+ for (var p in a) {
+ var k = a.constructor == Array ? a[p] : p;
+ null != k && k.constructor !== String && (k = void 0 === k.content ? String(k) : k.content);
+ this.addItem(k, a[p], b);
+ l++;
+ }
+ g.addEventListener("mouseleave", function(a) {
+ c.lock || c.close(a);
+ });
+ a = document;
+ b.event && (a = b.event.target.ownerDocument);
+ a || (a = document);
+ a.body.appendChild(g);
+ p = b.left || 0;
+ a = b.top || 0;
+ b.event && (p = b.event.pageX - 10, a = b.event.pageY - 10, b.title && (a -= 20), b.parentMenu && (b = b.parentMenu.root.getBoundingClientRect(), p = b.left + b.width), b = document.body.getBoundingClientRect(), l = g.getBoundingClientRect(), p > b.width - l.width - 10 && (p = b.width - l.width - 10), a > b.height - l.height - 10 && (a = b.height - l.height - 10));
+ g.style.left = p + "px";
+ g.style.top = a + "px";
+ }
+ var k = u.LiteGraph = {CANVAS_GRID_SIZE:10, NODE_TITLE_HEIGHT:20, NODE_SLOT_HEIGHT:15, NODE_WIDGET_HEIGHT:20, NODE_WIDTH:140, NODE_MIN_WIDTH:50, NODE_COLLAPSED_RADIUS:10, NODE_COLLAPSED_WIDTH:80, NODE_TITLE_COLOR:"#999", NODE_TEXT_SIZE:14, NODE_TEXT_COLOR:"#AAA", NODE_SUBTEXT_SIZE:12, NODE_DEFAULT_COLOR:"#333", NODE_DEFAULT_BGCOLOR:"#444", NODE_DEFAULT_BOXCOLOR:"#666", NODE_DEFAULT_SHAPE:"box", DEFAULT_SHADOW_COLOR:"rgba(0,0,0,0.5)", LINK_COLOR:"#AAD", EVENT_LINK_COLOR:"#F85", CONNECTING_LINK_COLOR:"#AFA",
+ MAX_NUMBER_OF_NODES:1000, DEFAULT_POSITION:[100, 100], VALID_SHAPES:["default", "box", "round", "card"], BOX_SHAPE:1, ROUND_SHAPE:2, CIRCLE_SHAPE:3, CARD_SHAPE:4, ARROW_SHAPE:5, INPUT:1, OUTPUT:2, EVENT:-1, ACTION:-1, ALWAYS:0, ON_EVENT:1, NEVER:2, ON_TRIGGER:3, UP:1, DOWN:2, LEFT:3, RIGHT:4, CENTER:5, NORMAL_TITLE:0, NO_TITLE:1, TRANSPARENT_TITLE:2, AUTOHIDE_TITLE:3, proxy:null, node_images_path:"", debug:!1, throw_errors:!0, allow_scripts:!0, registered_node_types:{}, node_types_by_file_extension:{},
+ Nodes:{}, registerNodeType:function(a, b) {
+ if (!b.prototype) {
+ throw "Cannot register a simple object, it must be a class with a prototype";
+ }
+ b.type = a;
+ k.debug && console.log("Node registered: " + a);
+ a.split("/");
+ var e = b.name, c = a.lastIndexOf("/");
+ b.category = a.substr(0, c);
+ b.title || (b.title = e);
+ if (b.prototype) {
+ for (var g in h.prototype) {
+ b.prototype[g] || (b.prototype[g] = h.prototype[g]);
+ }
+ }
+ Object.defineProperty(b.prototype, "shape", {set:function(a) {
+ switch(a) {
+ case "default":
+ delete this._shape;
+ break;
+ case "box":
+ this._shape = k.BOX_SHAPE;
+ break;
+ case "round":
+ this._shape = k.ROUND_SHAPE;
+ break;
+ case "circle":
+ this._shape = k.CIRCLE_SHAPE;
+ break;
+ case "card":
+ this._shape = k.CARD_SHAPE;
+ break;
+ default:
+ this._shape = a;
+ }
+ }, get:function(a) {
+ return this._shape;
+ }, enumerable:!0});
+ this.registered_node_types[a] = b;
+ b.constructor.name && (this.Nodes[e] = b);
+ b.prototype.onPropertyChange && console.warn("LiteGraph node class " + a + " has onPropertyChange method, it must be called onPropertyChanged with d at the end");
+ if (b.supported_extensions) {
+ for (g in b.supported_extensions) {
+ this.node_types_by_file_extension[b.supported_extensions[g].toLowerCase()] = b;
+ }
+ }
+ }, wrapFunctionAsNode:function(a, b, e, c) {
+ for (var g = Array(b.length), l = "", z = k.getParameterNames(b), d = 0; d < z.length; ++d) {
+ l += "this.addInput('" + z[d] + "'," + (e && e[d] ? "'" + e[d] + "'" : "0") + ");\n";
+ }
+ e = Function(l + ("this.addOutput('out'," + (c ? "'" + c + "'" : 0) + ");\n"));
+ e.title = a.split("/").pop();
+ e.desc = "Generated from " + b.name;
+ e.prototype.onExecute = function() {
+ for (var a = 0; a < g.length; ++a) {
+ g[a] = this.getInputData(a);
+ }
+ a = b.apply(this, g);
+ this.setOutputData(0, a);
+ };
+ this.registerNodeType(a, e);
+ }, addNodeMethod:function(a, b) {
+ h.prototype[a] = b;
+ for (var e in this.registered_node_types) {
+ var c = this.registered_node_types[e];
+ c.prototype[a] && (c.prototype["_" + a] = c.prototype[a]);
+ c.prototype[a] = b;
+ }
+ }, createNode:function(a, b, e) {
+ var c = this.registered_node_types[a];
+ if (!c) {
+ return k.debug && console.log('GraphNode type "' + a + '" not registered.'), null;
+ }
+ b = b || c.title || a;
+ c = new c(b);
+ c.type = a;
+ !c.title && b && (c.title = b);
+ c.properties || (c.properties = {});
+ c.properties_info || (c.properties_info = []);
+ c.flags || (c.flags = {});
+ c.size || (c.size = c.computeSize());
+ c.pos || (c.pos = k.DEFAULT_POSITION.concat());
+ c.mode || (c.mode = k.ALWAYS);
+ if (e) {
+ for (var g in e) {
+ c[g] = e[g];
+ }
+ }
+ return c;
+ }, getNodeType:function(a) {
+ return this.registered_node_types[a];
+ }, getNodeTypesInCategory:function(a, b) {
+ var e = [], c;
+ for (c in this.registered_node_types) {
+ var g = this.registered_node_types[c];
+ b && g.filter && g.filter != b || ("" == a ? null == g.category && e.push(g) : g.category == a && e.push(g));
+ }
+ return e;
+ }, getNodeTypesCategories:function() {
+ var a = {"":1}, b;
+ for (b in this.registered_node_types) {
+ this.registered_node_types[b].category && !this.registered_node_types[b].skip_list && (a[this.registered_node_types[b].category] = 1);
+ }
+ var e = [];
+ for (b in a) {
+ e.push(b);
+ }
+ return e;
+ }, reloadNodes:function(a) {
+ var b = document.getElementsByTagName("script"), e = [], c;
+ for (c in b) {
+ e.push(b[c]);
+ }
+ b = document.getElementsByTagName("head")[0];
+ a = document.location.href + a;
+ for (c in e) {
+ var g = e[c].src;
+ if (g && g.substr(0, a.length) == a) {
+ try {
+ k.debug && console.log("Reloading: " + g);
+ var l = document.createElement("script");
+ l.type = "text/javascript";
+ l.src = g;
+ b.appendChild(l);
+ b.removeChild(e[c]);
+ } catch (p) {
+ if (k.throw_errors) {
+ throw p;
+ }
+ k.debug && console.log("Error while reloading " + g);
+ }
+ }
+ }
+ k.debug && console.log("Nodes reloaded");
+ }, cloneObject:function(a, b) {
+ if (null == a) {
+ return null;
+ }
+ a = JSON.parse(JSON.stringify(a));
+ if (!b) {
+ return a;
+ }
+ for (var e in a) {
+ b[e] = a[e];
+ }
+ return b;
+ }, isValidConnection:function(a, b) {
+ if (!a || !b || a == b || a == k.EVENT && b == k.ACTION) {
+ return !0;
+ }
+ a = String(a);
+ b = String(b);
+ a = a.toLowerCase();
+ b = b.toLowerCase();
+ if (-1 == a.indexOf(",") && -1 == b.indexOf(",")) {
+ return a == b;
+ }
+ a = a.split(",");
+ b = b.split(",");
+ for (var e = 0; e < a.length; ++e) {
+ for (var c = 0; c < b.length; ++c) {
+ if (a[e] == b[c]) {
+ return !0;
+ }
+ }
+ }
+ return !1;
+ }};
+ k.getTime = "undefined" != typeof performance ? performance.now.bind(performance) : "undefined" != typeof Date && Date.now ? Date.now.bind(Date) : "undefined" != typeof process ? function() {
+ var a = process.hrtime();
+ return 0.001 * a[0] + 1e-6 * a[1];
+ } : function() {
+ return (new Date).getTime();
+ };
+ u.LGraph = k.LGraph = f;
+ f.supported_types = ["number", "string", "boolean"];
+ f.prototype.getSupportedTypes = function() {
+ return this.supported_types || f.supported_types;
+ };
+ f.STATUS_STOPPED = 1;
+ f.STATUS_RUNNING = 2;
+ f.prototype.clear = function() {
+ this.stop();
+ this.status = f.STATUS_STOPPED;
+ this.last_link_id = this.last_node_id = 1;
+ this._version = -1;
+ this._nodes = [];
+ this._nodes_by_id = {};
+ this._nodes_in_order = [];
+ this._nodes_executable = null;
+ this._groups = [];
+ this.links = {};
+ this.iteration = 0;
+ this.config = {};
+ this.fixedtime = this.runningtime = this.globaltime = 0;
+ this.elapsed_time = this.fixedtime_lapse = 0.01;
+ this.starttime = this.last_update_time = 0;
+ this.catch_errors = !0;
+ this.global_inputs = {};
+ this.global_outputs = {};
+ this.change();
+ this.sendActionToCanvas("clear");
+ };
+ f.prototype.attachCanvas = function(a) {
+ if (a.constructor != d) {
+ throw "attachCanvas expects a LGraphCanvas instance";
+ }
+ a.graph && a.graph != this && a.graph.detachCanvas(a);
+ a.graph = this;
+ this.list_of_graphcanvas || (this.list_of_graphcanvas = []);
+ this.list_of_graphcanvas.push(a);
+ };
+ f.prototype.detachCanvas = function(a) {
+ if (this.list_of_graphcanvas) {
+ var b = this.list_of_graphcanvas.indexOf(a);
+ -1 != b && (a.graph = null, this.list_of_graphcanvas.splice(b, 1));
+ }
+ };
+ f.prototype.start = function(a) {
+ if (this.status != f.STATUS_RUNNING) {
+ this.status = f.STATUS_RUNNING;
+ if (this.onPlayEvent) {
+ this.onPlayEvent();
+ }
+ this.sendEventToAllNodes("onStart");
+ this.last_update_time = this.starttime = k.getTime();
+ var b = this;
+ this.execution_timer_id = setInterval(function() {
+ b.runStep(1, !this.catch_errors);
+ }, a || 1);
+ }
+ };
+ f.prototype.stop = function() {
+ if (this.status != f.STATUS_STOPPED) {
+ this.status = f.STATUS_STOPPED;
+ if (this.onStopEvent) {
+ this.onStopEvent();
+ }
+ null != this.execution_timer_id && clearInterval(this.execution_timer_id);
+ this.execution_timer_id = null;
+ this.sendEventToAllNodes("onStop");
+ }
+ };
+ f.prototype.runStep = function(a, b) {
+ a = a || 1;
+ var e = k.getTime();
+ this.globaltime = 0.001 * (e - this.starttime);
+ var c = this._nodes_executable ? this._nodes_executable : this._nodes;
+ if (c) {
+ if (b) {
+ for (var g = 0; g < a; g++) {
+ for (var l = 0, p = c.length; l < p; ++l) {
+ var d = c[l];
+ if (d.mode == k.ALWAYS && d.onExecute) {
+ d.onExecute();
+ }
+ }
+ this.fixedtime += this.fixedtime_lapse;
+ if (this.onExecuteStep) {
+ this.onExecuteStep();
+ }
+ }
+ if (this.onAfterExecute) {
+ this.onAfterExecute();
+ }
+ } else {
+ try {
+ for (g = 0; g < a; g++) {
+ l = 0;
+ for (p = c.length; l < p; ++l) {
+ if (d = c[l], d.mode == k.ALWAYS && d.onExecute) {
+ d.onExecute();
+ }
+ }
+ this.fixedtime += this.fixedtime_lapse;
+ if (this.onExecuteStep) {
+ this.onExecuteStep();
+ }
+ }
+ if (this.onAfterExecute) {
+ this.onAfterExecute();
+ }
+ this.errors_in_execution = !1;
+ } catch (G) {
+ this.errors_in_execution = !0;
+ if (k.throw_errors) {
+ throw G;
+ }
+ k.debug && console.log("Error during execution: " + G);
+ this.stop();
+ }
+ }
+ a = k.getTime();
+ e = a - e;
+ 0 == e && (e = 1);
+ this.execution_time = 0.001 * e;
+ this.globaltime += 0.001 * e;
+ this.iteration += 1;
+ this.elapsed_time = 0.001 * (a - this.last_update_time);
+ this.last_update_time = a;
+ }
+ };
+ f.prototype.updateExecutionOrder = function() {
+ this._nodes_in_order = this.computeExecutionOrder(!1);
+ this._nodes_executable = [];
+ for (var a = 0; a < this._nodes_in_order.length; ++a) {
+ this._nodes_in_order[a].onExecute && this._nodes_executable.push(this._nodes_in_order[a]);
+ }
+ };
+ f.prototype.computeExecutionOrder = function(a, b) {
+ for (var e = [], c = [], g = {}, l = {}, p = {}, d = 0, m = this._nodes.length; d < m; ++d) {
+ var f = this._nodes[d];
+ if (!a || f.onExecute) {
+ g[f.id] = f;
+ var n = 0;
+ if (f.inputs) {
+ for (var h = 0, q = f.inputs.length; h < q; h++) {
+ f.inputs[h] && null != f.inputs[h].link && (n += 1);
+ }
+ }
+ 0 == n ? (c.push(f), b && (f._level = 1)) : (b && (f._level = 0), p[f.id] = n);
+ }
+ }
+ for (; 0 != c.length;) {
+ if (f = c.shift(), e.push(f), delete g[f.id], f.outputs) {
+ for (d = 0; d < f.outputs.length; d++) {
+ if (a = f.outputs[d], null != a && null != a.links && 0 != a.links.length) {
+ for (h = 0; h < a.links.length; h++) {
+ (m = this.links[a.links[h]]) && !l[m.id] && (n = this.getNodeById(m.target_id), null == n ? l[m.id] = !0 : (b && (!n._level || n._level <= f._level) && (n._level = f._level + 1), l[m.id] = !0, --p[n.id], 0 == p[n.id] && c.push(n)));
+ }
+ }
+ }
+ }
+ }
+ for (d in g) {
+ e.push(g[d]);
+ }
+ e.length != this._nodes.length && k.debug && console.warn("something went wrong, nodes missing");
+ m = e.length;
+ for (d = 0; d < m; ++d) {
+ e[d].order = d;
+ }
+ e = e.sort(function(a, b) {
+ var e = a.constructor.priority || a.priority || 0, c = b.constructor.priority || b.priority || 0;
+ return e == c ? a.order - b.order : e - c;
+ });
+ for (d = 0; d < m; ++d) {
+ e[d].order = d;
+ }
+ return e;
+ };
+ f.prototype.getAncestors = function(a) {
+ for (var b = [], e = [a], c = {}; e.length;) {
+ var g = e.shift();
+ if (g.inputs) {
+ c[g.id] || g == a || (c[g.id] = !0, b.push(g));
+ for (var l = 0; l < g.inputs.length; ++l) {
+ var p = g.getInputNode(l);
+ p && -1 == b.indexOf(p) && e.push(p);
+ }
+ }
+ }
+ b.sort(function(a, b) {
+ return a.order - b.order;
+ });
+ return b;
+ };
+ f.prototype.arrange = function(a) {
+ a = a || 40;
+ for (var b = this.computeExecutionOrder(!1, !0), e = [], c = 0; c < b.length; ++c) {
+ var g = b[c], l = g._level || 1;
+ e[l] || (e[l] = []);
+ e[l].push(g);
+ }
+ b = a;
+ for (c = 0; c < e.length; ++c) {
+ if (l = e[c]) {
+ for (var p = 100, k = a, d = 0; d < l.length; ++d) {
+ g = l[d], g.pos[0] = b, g.pos[1] = k, g.size[0] > p && (p = g.size[0]), k += g.size[1] + a;
+ }
+ b += p + a;
+ }
+ }
+ this.setDirtyCanvas(!0, !0);
+ };
+ f.prototype.getTime = function() {
+ return this.globaltime;
+ };
+ f.prototype.getFixedTime = function() {
+ return this.fixedtime;
+ };
+ f.prototype.getElapsedTime = function() {
+ return this.elapsed_time;
+ };
+ f.prototype.sendEventToAllNodes = function(a, b, e) {
+ e = e || k.ALWAYS;
+ var c = this._nodes_in_order ? this._nodes_in_order : this._nodes;
+ if (c) {
+ for (var g = 0, l = c.length; g < l; ++g) {
+ var p = c[g];
+ if (p[a] && p.mode == e) {
+ if (void 0 === b) {
+ p[a]();
+ } else {
+ if (b && b.constructor === Array) {
+ p[a].apply(p, b);
+ } else {
+ p[a](b);
+ }
+ }
+ }
+ }
+ }
+ };
+ f.prototype.sendActionToCanvas = function(a, b) {
+ if (this.list_of_graphcanvas) {
+ for (var e = 0; e < this.list_of_graphcanvas.length; ++e) {
+ var c = this.list_of_graphcanvas[e];
+ c[a] && c[a].apply(c, b);
+ }
+ }
+ };
+ f.prototype.add = function(a, b) {
+ if (a) {
+ if (a.constructor === n) {
+ this._groups.push(a), this.setDirtyCanvas(!0), this.change(), a.graph = this, this._version++;
+ } else {
+ -1 != a.id && null != this._nodes_by_id[a.id] && (console.warn("LiteGraph: there is already a node with this ID, changing it"), a.id = ++this.last_node_id);
+ if (this._nodes.length >= k.MAX_NUMBER_OF_NODES) {
+ throw "LiteGraph: max number of nodes in a graph reached";
+ }
+ null == a.id || -1 == a.id ? a.id = ++this.last_node_id : this.last_node_id < a.id && (this.last_node_id = a.id);
+ a.graph = this;
+ this._version++;
+ this._nodes.push(a);
+ this._nodes_by_id[a.id] = a;
+ if (a.onAdded) {
+ a.onAdded(this);
+ }
+ this.config.align_to_grid && a.alignToGrid();
+ b || this.updateExecutionOrder();
+ if (this.onNodeAdded) {
+ this.onNodeAdded(a);
+ }
+ this.setDirtyCanvas(!0);
+ this.change();
+ return a;
+ }
+ }
+ };
+ f.prototype.remove = function(a) {
+ if (a.constructor === k.LGraphGroup) {
+ var b = this._groups.indexOf(a);
+ -1 != b && this._groups.splice(b, 1);
+ a.graph = null;
+ this._version++;
+ this.setDirtyCanvas(!0, !0);
+ this.change();
+ } else {
+ if (null != this._nodes_by_id[a.id] && !a.ignore_remove) {
+ if (a.inputs) {
+ for (b = 0; b < a.inputs.length; b++) {
+ var e = a.inputs[b];
+ null != e.link && a.disconnectInput(b);
+ }
+ }
+ if (a.outputs) {
+ for (b = 0; b < a.outputs.length; b++) {
+ e = a.outputs[b], null != e.links && e.links.length && a.disconnectOutput(b);
+ }
+ }
+ if (a.onRemoved) {
+ a.onRemoved();
+ }
+ a.graph = null;
+ this._version++;
+ if (this.list_of_graphcanvas) {
+ for (b = 0; b < this.list_of_graphcanvas.length; ++b) {
+ e = this.list_of_graphcanvas[b], e.selected_nodes[a.id] && delete e.selected_nodes[a.id], e.node_dragged == a && (e.node_dragged = null);
+ }
+ }
+ b = this._nodes.indexOf(a);
+ -1 != b && this._nodes.splice(b, 1);
+ delete this._nodes_by_id[a.id];
+ if (this.onNodeRemoved) {
+ this.onNodeRemoved(a);
+ }
+ this.setDirtyCanvas(!0, !0);
+ this.change();
+ this.updateExecutionOrder();
+ }
+ }
+ };
+ f.prototype.getNodeById = function(a) {
+ return null == a ? null : this._nodes_by_id[a];
+ };
+ f.prototype.findNodesByClass = function(a) {
+ for (var b = [], e = 0, c = this._nodes.length; e < c; ++e) {
+ this._nodes[e].constructor === a && b.push(this._nodes[e]);
+ }
+ return b;
+ };
+ f.prototype.findNodesByType = function(a) {
+ a = a.toLowerCase();
+ for (var b = [], e = 0, c = this._nodes.length; e < c; ++e) {
+ this._nodes[e].type.toLowerCase() == a && b.push(this._nodes[e]);
+ }
+ return b;
+ };
+ f.prototype.findNodesByTitle = function(a) {
+ for (var b = [], e = 0, c = this._nodes.length; e < c; ++e) {
+ this._nodes[e].title == a && b.push(this._nodes[e]);
+ }
+ return b;
+ };
+ f.prototype.getNodeOnPos = function(a, b, e) {
+ e = e || this._nodes;
+ for (var c = e.length - 1; 0 <= c; c--) {
+ var g = e[c];
+ if (g.isPointInside(a, b, 2)) {
+ return g;
+ }
+ }
+ return null;
+ };
+ f.prototype.getGroupOnPos = function(a, b) {
+ for (var e = this._groups.length - 1; 0 <= e; e--) {
+ var c = this._groups[e];
+ if (c.isPointInside(a, b, 2)) {
+ return c;
+ }
+ }
+ return null;
+ };
+ f.prototype.addGlobalInput = function(a, b, e) {
+ this.global_inputs[a] = {name:a, type:b, value:e};
+ this._version++;
+ if (this.onGlobalInputAdded) {
+ this.onGlobalInputAdded(a, b);
+ }
+ if (this.onGlobalsChange) {
+ this.onGlobalsChange();
+ }
+ };
+ f.prototype.setGlobalInputData = function(a, b) {
+ if (a = this.global_inputs[a]) {
+ a.value = b;
+ }
+ };
+ f.prototype.setInputData = f.prototype.setGlobalInputData;
+ f.prototype.getGlobalInputData = function(a) {
+ return (a = this.global_inputs[a]) ? a.value : null;
+ };
+ f.prototype.renameGlobalInput = function(a, b) {
+ if (b != a) {
+ if (!this.global_inputs[a]) {
+ return !1;
+ }
+ if (this.global_inputs[b]) {
+ return console.error("there is already one input with that name"), !1;
+ }
+ this.global_inputs[b] = this.global_inputs[a];
+ delete this.global_inputs[a];
+ this._version++;
+ if (this.onGlobalInputRenamed) {
+ this.onGlobalInputRenamed(a, b);
+ }
+ if (this.onGlobalsChange) {
+ this.onGlobalsChange();
+ }
+ }
+ };
+ f.prototype.changeGlobalInputType = function(a, b) {
+ if (!this.global_inputs[a]) {
+ return !1;
+ }
+ if (!this.global_inputs[a].type || this.global_inputs[a].type.toLowerCase() != b.toLowerCase()) {
+ if (this.global_inputs[a].type = b, this._version++, this.onGlobalInputTypeChanged) {
+ this.onGlobalInputTypeChanged(a, b);
+ }
+ }
+ };
+ f.prototype.removeGlobalInput = function(a) {
+ if (!this.global_inputs[a]) {
+ return !1;
+ }
+ delete this.global_inputs[a];
+ this._version++;
+ if (this.onGlobalInputRemoved) {
+ this.onGlobalInputRemoved(a);
+ }
+ if (this.onGlobalsChange) {
+ this.onGlobalsChange();
+ }
+ return !0;
+ };
+ f.prototype.addGlobalOutput = function(a, b, e) {
+ this.global_outputs[a] = {name:a, type:b, value:e};
+ this._version++;
+ if (this.onGlobalOutputAdded) {
+ this.onGlobalOutputAdded(a, b);
+ }
+ if (this.onGlobalsChange) {
+ this.onGlobalsChange();
+ }
+ };
+ f.prototype.setGlobalOutputData = function(a, b) {
+ if (a = this.global_outputs[a]) {
+ a.value = b;
+ }
+ };
+ f.prototype.getGlobalOutputData = function(a) {
+ return (a = this.global_outputs[a]) ? a.value : null;
+ };
+ f.prototype.getOutputData = f.prototype.getGlobalOutputData;
+ f.prototype.renameGlobalOutput = function(a, b) {
+ if (!this.global_outputs[a]) {
+ return !1;
+ }
+ if (this.global_outputs[b]) {
+ return console.error("there is already one output with that name"), !1;
+ }
+ this.global_outputs[b] = this.global_outputs[a];
+ delete this.global_outputs[a];
+ this._version++;
+ if (this.onGlobalOutputRenamed) {
+ this.onGlobalOutputRenamed(a, b);
+ }
+ if (this.onGlobalsChange) {
+ this.onGlobalsChange();
+ }
+ };
+ f.prototype.changeGlobalOutputType = function(a, b) {
+ if (!this.global_outputs[a]) {
+ return !1;
+ }
+ if (!this.global_outputs[a].type || this.global_outputs[a].type.toLowerCase() != b.toLowerCase()) {
+ if (this.global_outputs[a].type = b, this._version++, this.onGlobalOutputTypeChanged) {
+ this.onGlobalOutputTypeChanged(a, b);
+ }
+ }
+ };
+ f.prototype.removeGlobalOutput = function(a) {
+ if (!this.global_outputs[a]) {
+ return !1;
+ }
+ delete this.global_outputs[a];
+ this._version++;
+ if (this.onGlobalOutputRemoved) {
+ this.onGlobalOutputRemoved(a);
+ }
+ if (this.onGlobalsChange) {
+ this.onGlobalsChange();
+ }
+ return !0;
+ };
+ f.prototype.triggerInput = function(a, b) {
+ a = this.findNodesByTitle(a);
+ for (var e = 0; e < a.length; ++e) {
+ a[e].onTrigger(b);
+ }
+ };
+ f.prototype.setCallback = function(a, b) {
+ a = this.findNodesByTitle(a);
+ for (var e = 0; e < a.length; ++e) {
+ a[e].setTrigger(b);
+ }
+ };
+ f.prototype.connectionChange = function(a) {
+ this.updateExecutionOrder();
+ if (this.onConnectionChange) {
+ this.onConnectionChange(a);
+ }
+ this._version++;
+ this.sendActionToCanvas("onConnectionChange");
+ };
+ f.prototype.isLive = function() {
+ if (!this.list_of_graphcanvas) {
+ return !1;
+ }
+ for (var a = 0; a < this.list_of_graphcanvas.length; ++a) {
+ if (this.list_of_graphcanvas[a].live_mode) {
+ return !0;
+ }
+ }
+ return !1;
+ };
+ f.prototype.change = function() {
+ k.debug && console.log("Graph changed");
+ this.sendActionToCanvas("setDirty", [!0, !0]);
+ if (this.on_change) {
+ this.on_change(this);
+ }
+ };
+ f.prototype.setDirtyCanvas = function(a, b) {
+ this.sendActionToCanvas("setDirty", [a, b]);
+ };
+ f.prototype.serialize = function() {
+ for (var a = [], b = 0, e = this._nodes.length; b < e; ++b) {
+ a.push(this._nodes[b].serialize());
+ }
+ e = [];
+ for (b in this.links) {
+ var c = this.links[b];
+ e.push([c.id, c.origin_id, c.origin_slot, c.target_id, c.target_slot, c.type]);
+ }
+ c = [];
+ for (b = 0; b < this._groups.length; ++b) {
+ c.push(this._groups[b].serialize());
+ }
+ return {last_node_id:this.last_node_id, last_link_id:this.last_link_id, nodes:a, links:e, groups:c, config:this.config};
+ };
+ f.prototype.configure = function(a, b) {
+ if (a) {
+ b || this.clear();
+ b = a.nodes;
+ if (a.links && a.links.constructor === Array) {
+ for (var e = [], c = 0; c < a.links.length; ++c) {
+ var g = a.links[c];
+ e[g[0]] = {id:g[0], origin_id:g[1], origin_slot:g[2], target_id:g[3], target_slot:g[4], type:g[5]};
+ }
+ a.links = e;
+ }
+ for (c in a) {
+ this[c] = a[c];
+ }
+ e = !1;
+ this._nodes = [];
+ if (b) {
+ c = 0;
+ for (g = b.length; c < g; ++c) {
+ var l = b[c], p = k.createNode(l.type, l.title);
+ p ? (p.id = l.id, this.add(p, !0)) : (k.debug && console.log("Node not found: " + l.type), e = !0);
+ }
+ c = 0;
+ for (g = b.length; c < g; ++c) {
+ l = b[c], (p = this.getNodeById(l.id)) && p.configure(l);
+ }
+ }
+ this._groups.length = 0;
+ if (a.groups) {
+ for (c = 0; c < a.groups.length; ++c) {
+ b = new k.LGraphGroup, b.configure(a.groups[c]), this.add(b);
+ }
+ }
+ this.updateExecutionOrder();
+ this._version++;
+ this.setDirtyCanvas(!0, !0);
+ return e;
+ }
+ };
+ f.prototype.load = function(a) {
+ var b = this, e = new XMLHttpRequest;
+ e.open("GET", a, !0);
+ e.send(null);
+ e.onload = function(a) {
+ 200 !== e.status ? console.error("Error loading graph:", e.status, e.response) : (a = JSON.parse(e.response), b.configure(a));
+ };
+ e.onerror = function(a) {
+ console.error("Error loading graph:", a);
+ };
+ };
+ f.prototype.onNodeTrace = function(a, b, e) {
+ };
+ u.LGraphNode = k.LGraphNode = h;
+ h.prototype._ctor = function(a) {
+ this.title = a || "Unnamed";
+ this.size = [k.NODE_WIDTH, 60];
+ this.graph = null;
+ this._pos = new Float32Array(10, 10);
+ Object.defineProperty(this, "pos", {set:function(a) {
+ !a || 2 > a.length || (this._pos[0] = a[0], this._pos[1] = a[1]);
+ }, get:function() {
+ return this._pos;
+ }, enumerable:!0});
+ this.id = -1;
+ this.type = null;
+ this.inputs = [];
+ this.outputs = [];
+ this.connections = [];
+ this.properties = {};
+ this.properties_info = [];
+ this.data = null;
+ this.flags = {};
+ };
+ h.prototype.configure = function(a) {
+ this.graph && this.graph._version++;
+ for (var b in a) {
+ if ("console" != b) {
+ if ("properties" == b) {
+ for (var e in a.properties) {
+ if (this.properties[e] = a.properties[e], this.onPropertyChanged) {
+ this.onPropertyChanged(e, a.properties[e]);
+ }
+ }
+ } else {
+ null != a[b] && ("object" == typeof a[b] ? this[b] && this[b].configure ? this[b].configure(a[b]) : this[b] = k.cloneObject(a[b], this[b]) : this[b] = a[b]);
+ }
+ }
+ }
+ a.title || (this.title = this.constructor.title);
+ if (this.onConnectionsChange) {
+ if (this.inputs) {
+ for (var c = 0; c < this.inputs.length; ++c) {
+ e = this.inputs[c];
+ var g = this.graph ? this.graph.links[e.link] : null;
+ this.onConnectionsChange(k.INPUT, c, !0, g, e);
+ }
+ }
+ if (this.outputs) {
+ for (c = 0; c < this.outputs.length; ++c) {
+ if (e = this.outputs[c], e.links) {
+ for (b = 0; b < e.links.length; ++b) {
+ g = this.graph ? this.graph.links[e.links[b]] : null, this.onConnectionsChange(k.OUTPUT, c, !0, g, e);
+ }
+ }
+ }
+ }
+ }
+ for (c in this.inputs) {
+ e = this.inputs[c], e.link && e.link.length && (g = e.link, "object" == typeof g && (e.link = g[0], this.graph && (this.graph.links[g[0]] = {id:g[0], origin_id:g[1], origin_slot:g[2], target_id:g[3], target_slot:g[4]})));
+ }
+ for (c in this.outputs) {
+ if (e = this.outputs[c], e.links && 0 != e.links.length) {
+ for (b in e.links) {
+ g = e.links[b], "object" == typeof g && (e.links[b] = g[0]);
+ }
+ }
+ }
+ if (this.onConfigure) {
+ this.onConfigure(a);
+ }
+ };
+ h.prototype.serialize = function() {
+ var a = {id:this.id, type:this.type, pos:this.pos, size:this.size, data:this.data, flags:k.cloneObject(this.flags), mode:this.mode};
+ this.inputs && (a.inputs = this.inputs);
+ if (this.outputs) {
+ for (var b = 0; b < this.outputs.length; b++) {
+ delete this.outputs[b]._data;
+ }
+ a.outputs = this.outputs;
+ }
+ this.title && this.title != this.constructor.title && (a.title = this.title);
+ this.properties && (a.properties = k.cloneObject(this.properties));
+ a.type || (a.type = this.constructor.type);
+ this.color && (a.color = this.color);
+ this.bgcolor && (a.bgcolor = this.bgcolor);
+ this.boxcolor && (a.boxcolor = this.boxcolor);
+ this.shape && (a.shape = this.shape);
+ if (this.onSerialize) {
+ this.onSerialize(a);
+ }
+ return a;
+ };
+ h.prototype.clone = function() {
+ var a = k.createNode(this.type), b = k.cloneObject(this.serialize());
+ if (b.inputs) {
+ for (var e = 0; e < b.inputs.length; ++e) {
+ b.inputs[e].link = null;
+ }
+ }
+ if (b.outputs) {
+ for (e = 0; e < b.outputs.length; ++e) {
+ b.outputs[e].links && (b.outputs[e].links.length = 0);
+ }
+ }
+ delete b.id;
+ a.configure(b);
+ return a;
+ };
+ h.prototype.toString = function() {
+ return JSON.stringify(this.serialize());
+ };
+ h.prototype.getTitle = function() {
+ return this.title || this.constructor.title;
+ };
+ h.prototype.setOutputData = function(a, b) {
+ if (this.outputs && !(-1 == a || a >= this.outputs.length)) {
+ var e = this.outputs[a];
+ if (e && (e._data = b, this.outputs[a].links)) {
+ for (e = 0; e < this.outputs[a].links.length; e++) {
+ this.graph.links[this.outputs[a].links[e]].data = b;
+ }
+ }
+ }
+ };
+ h.prototype.getInputData = function(a, b) {
+ if (this.inputs && !(a >= this.inputs.length || null == this.inputs[a].link)) {
+ a = this.graph.links[this.inputs[a].link];
+ if (!a) {
+ return null;
+ }
+ if (!b) {
+ return a.data;
+ }
+ b = this.graph.getNodeById(a.origin_id);
+ if (!b) {
+ return a.data;
+ }
+ if (b.updateOutputData) {
+ b.updateOutputData(a.origin_slot);
+ } else {
+ if (b.onExecute) {
+ b.onExecute();
+ }
+ }
+ return a.data;
+ }
+ };
+ h.prototype.getInputDataByName = function(a, b) {
+ a = this.findInputSlot(a);
+ return -1 == a ? null : this.getInputData(a, b);
+ };
+ h.prototype.isInputConnected = function(a) {
+ return this.inputs ? a < this.inputs.length && null != this.inputs[a].link : !1;
+ };
+ h.prototype.getInputInfo = function(a) {
+ return this.inputs ? a < this.inputs.length ? this.inputs[a] : null : null;
+ };
+ h.prototype.getInputNode = function(a) {
+ if (!this.inputs || a >= this.inputs.length) {
+ return null;
+ }
+ a = this.inputs[a];
+ return a && null !== a.link ? (a = this.graph.links[a.link]) ? this.graph.getNodeById(a.origin_id) : null : null;
+ };
+ h.prototype.getInputOrProperty = function(a) {
+ if (!this.inputs || !this.inputs.length) {
+ return this.properties ? this.properties[a] : null;
+ }
+ for (var b = 0, e = this.inputs.length; b < e; ++b) {
+ if (a == this.inputs[b].name) {
+ return (a = this.graph.links[this.inputs[b].link]) ? a.data : null;
+ }
+ }
+ return this.properties[a];
+ };
+ h.prototype.getOutputData = function(a) {
+ return !this.outputs || a >= this.outputs.length ? null : this.outputs[a]._data;
+ };
+ h.prototype.getOutputInfo = function(a) {
+ return this.outputs ? a < this.outputs.length ? this.outputs[a] : null : null;
+ };
+ h.prototype.isOutputConnected = function(a) {
+ return this.outputs ? a < this.outputs.length && this.outputs[a].links && this.outputs[a].links.length : !1;
+ };
+ h.prototype.isAnyOutputConnected = function() {
+ if (!this.outputs) {
+ return !1;
+ }
+ for (var a = 0; a < this.outputs.length; ++a) {
+ if (this.outputs[a].links && this.outputs[a].links.length) {
+ return !0;
+ }
+ }
+ return !1;
+ };
+ h.prototype.getOutputNodes = function(a) {
+ if (!this.outputs || 0 == this.outputs.length || a >= this.outputs.length) {
+ return null;
+ }
+ a = this.outputs[a];
+ if (!a.links || 0 == a.links.length) {
+ return null;
+ }
+ for (var b = [], e = 0; e < a.links.length; e++) {
+ var c = this.graph.links[a.links[e]];
+ c && (c = this.graph.getNodeById(c.target_id)) && b.push(c);
+ }
+ return b;
+ };
+ h.prototype.trigger = function(a, b) {
+ if (this.outputs && this.outputs.length) {
+ this.graph && (this.graph._last_trigger_time = k.getTime());
+ for (var e = 0; e < this.outputs.length; ++e) {
+ var c = this.outputs[e];
+ !c || c.type !== k.EVENT || a && c.name != a || this.triggerSlot(e, b);
+ }
+ }
+ };
+ h.prototype.triggerSlot = function(a, b) {
+ if (this.outputs && (a = this.outputs[a]) && (a = a.links) && a.length) {
+ this.graph && (this.graph._last_trigger_time = k.getTime());
+ for (var e = 0; e < a.length; ++e) {
+ var c = this.graph.links[a[e]];
+ if (c) {
+ var g = this.graph.getNodeById(c.target_id);
+ if (g) {
+ if (c._last_time = k.getTime(), c = g.inputs[c.target_slot], g.onAction) {
+ g.onAction(c.name, b);
+ } else {
+ if (g.mode === k.ON_TRIGGER && g.onExecute) {
+ g.onExecute(b);
+ }
+ }
+ }
+ }
+ }
+ }
+ };
+ h.prototype.addProperty = function(a, b, e, c) {
+ e = {name:a, type:e, default_value:b};
+ if (c) {
+ for (var g in c) {
+ e[g] = c[g];
+ }
+ }
+ this.properties_info || (this.properties_info = []);
+ this.properties_info.push(e);
+ this.properties || (this.properties = {});
+ this.properties[a] = b;
+ return e;
+ };
+ h.prototype.addOutput = function(a, b, e) {
+ a = {name:a, type:b, links:null};
+ if (e) {
+ for (var c in e) {
+ a[c] = e[c];
+ }
+ }
+ this.outputs || (this.outputs = []);
+ this.outputs.push(a);
+ if (this.onOutputAdded) {
+ this.onOutputAdded(a);
+ }
+ this.size = this.computeSize();
+ return a;
+ };
+ h.prototype.addOutputs = function(a) {
+ for (var b = 0; b < a.length; ++b) {
+ var e = a[b], c = {name:e[0], type:e[1], link:null};
+ if (a[2]) {
+ for (var g in e[2]) {
+ c[g] = e[2][g];
+ }
+ }
+ this.outputs || (this.outputs = []);
+ this.outputs.push(c);
+ if (this.onOutputAdded) {
+ this.onOutputAdded(c);
+ }
+ }
+ this.size = this.computeSize();
+ };
+ h.prototype.removeOutput = function(a) {
+ this.disconnectOutput(a);
+ this.outputs.splice(a, 1);
+ this.size = this.computeSize();
+ if (this.onOutputRemoved) {
+ this.onOutputRemoved(a);
+ }
+ };
+ h.prototype.addInput = function(a, b, e) {
+ a = {name:a, type:b || 0, link:null};
+ if (e) {
+ for (var c in e) {
+ a[c] = e[c];
+ }
+ }
+ this.inputs || (this.inputs = []);
+ this.inputs.push(a);
+ this.size = this.computeSize();
+ if (this.onInputAdded) {
+ this.onInputAdded(a);
+ }
+ return a;
+ };
+ h.prototype.addInputs = function(a) {
+ for (var b = 0; b < a.length; ++b) {
+ var e = a[b], c = {name:e[0], type:e[1], link:null};
+ if (a[2]) {
+ for (var g in e[2]) {
+ c[g] = e[2][g];
+ }
+ }
+ this.inputs || (this.inputs = []);
+ this.inputs.push(c);
+ if (this.onInputAdded) {
+ this.onInputAdded(c);
+ }
+ }
+ this.size = this.computeSize();
+ };
+ h.prototype.removeInput = function(a) {
+ this.disconnectInput(a);
+ this.inputs.splice(a, 1);
+ this.size = this.computeSize();
+ if (this.onInputRemoved) {
+ this.onInputRemoved(a);
+ }
+ };
+ h.prototype.addConnection = function(a, b, e, c) {
+ a = {name:a, type:b, pos:e, direction:c, links:null};
+ this.connections.push(a);
+ return a;
+ };
+ h.prototype.computeSize = function(a, b) {
+ function e(a) {
+ return a ? c * a.length * 0.6 : 0;
+ }
+ a = Math.max(this.inputs ? this.inputs.length : 1, this.outputs ? this.outputs.length : 1);
+ b = b || new Float32Array([0, 0]);
+ a = Math.max(a, 1);
+ var c = k.NODE_TEXT_SIZE;
+ b[1] = (this.constructor.slot_start_y || 0) + a * (c + 1) + (this.widgets ? this.widgets.length : 0) * (k.NODE_WIDGET_HEIGHT + 4) + 4;
+ a = e(this.title);
+ var g = 0, l = 0;
+ if (this.inputs) {
+ for (var p = 0, d = this.inputs.length; p < d; ++p) {
+ var m = this.inputs[p];
+ m = m.label || m.name || "";
+ m = e(m);
+ g < m && (g = m);
+ }
+ }
+ if (this.outputs) {
+ for (p = 0, d = this.outputs.length; p < d; ++p) {
+ m = this.outputs[p], m = m.label || m.name || "", m = e(m), l < m && (l = m);
+ }
+ }
+ b[0] = Math.max(g + l + 10, a);
+ b[0] = Math.max(b[0], k.NODE_WIDTH);
+ if (this.onResize) {
+ this.onResize(b);
+ }
+ return b;
+ };
+ h.prototype.addWidget = function(a, b, e, c, g) {
+ this.widgets || (this.widgets = []);
+ b = {type:a.toLowerCase(), name:b, value:e, callback:c, options:g || {}};
+ void 0 !== b.options.y && (b.y = b.options.y);
+ if ("combo" == a && !b.options.values) {
+ throw "LiteGraph addWidget('combo',...) requires to pass values in options: { values:['red','blue'] }";
+ }
+ this.widgets.push(b);
+ return b;
+ };
+ h.prototype.getBounding = function(a) {
+ a = a || new Float32Array(4);
+ a[0] = this.pos[0] - 4;
+ a[1] = this.pos[1] - k.NODE_TITLE_HEIGHT;
+ a[2] = this.size[0] + 4;
+ a[3] = this.size[1] + k.NODE_TITLE_HEIGHT;
+ return a;
+ };
+ h.prototype.isPointInside = function(a, b, e) {
+ e = e || 0;
+ var c = this.graph && this.graph.isLive() ? 0 : 20;
+ if (this.flags && this.flags.collapsed) {
+ if (t(a, b, this.pos[0] - e, this.pos[1] - k.NODE_TITLE_HEIGHT - e, (this._collapsed_width || k.NODE_COLLAPSED_WIDTH) + 2 * e, k.NODE_TITLE_HEIGHT + 2 * e)) {
+ return !0;
+ }
+ } else {
+ if (this.pos[0] - 4 - e < a && this.pos[0] + this.size[0] + 4 + e > a && this.pos[1] - c - e < b && this.pos[1] + this.size[1] + e > b) {
+ return !0;
+ }
+ }
+ return !1;
+ };
+ h.prototype.getSlotInPosition = function(a, b) {
+ if (this.inputs) {
+ for (var e = 0, c = this.inputs.length; e < c; ++e) {
+ var g = this.inputs[e], l = this.getConnectionPos(!0, e);
+ if (t(a, b, l[0] - 10, l[1] - 5, 20, 10)) {
+ return {input:g, slot:e, link_pos:l, locked:g.locked};
+ }
+ }
+ }
+ if (this.outputs) {
+ for (e = 0, c = this.outputs.length; e < c; ++e) {
+ if (g = this.outputs[e], l = this.getConnectionPos(!1, e), t(a, b, l[0] - 10, l[1] - 5, 20, 10)) {
+ return {output:g, slot:e, link_pos:l, locked:g.locked};
+ }
+ }
+ }
+ return null;
+ };
+ h.prototype.findInputSlot = function(a) {
+ if (!this.inputs) {
+ return -1;
+ }
+ for (var b = 0, e = this.inputs.length; b < e; ++b) {
+ if (a == this.inputs[b].name) {
+ return b;
+ }
+ }
+ return -1;
+ };
+ h.prototype.findOutputSlot = function(a) {
+ if (!this.outputs) {
+ return -1;
+ }
+ for (var b = 0, e = this.outputs.length; b < e; ++b) {
+ if (a == this.outputs[b].name) {
+ return b;
+ }
+ }
+ return -1;
+ };
+ h.prototype.connect = function(a, b, e) {
+ e = e || 0;
+ if (!this.graph) {
+ return console.log("Connect: Error, node doesnt belong to any graph. Nodes must be added first to a graph before connecting them."), !1;
+ }
+ if (a.constructor === String) {
+ if (a = this.findOutputSlot(a), -1 == a) {
+ return k.debug && console.log("Connect: Error, no slot of name " + a), !1;
+ }
+ } else {
+ if (!this.outputs || a >= this.outputs.length) {
+ return k.debug && console.log("Connect: Error, slot number not found"), !1;
+ }
+ }
+ b && b.constructor === Number && (b = this.graph.getNodeById(b));
+ if (!b) {
+ throw "target node is null";
+ }
+ if (b == this) {
+ return !1;
+ }
+ if (e.constructor === String) {
+ if (e = b.findInputSlot(e), -1 == e) {
+ return k.debug && console.log("Connect: Error, no slot of name " + e), !1;
+ }
+ } else {
+ if (e === k.EVENT) {
+ return !1;
+ }
+ if (!b.inputs || e >= b.inputs.length) {
+ return k.debug && console.log("Connect: Error, slot number not found"), !1;
+ }
+ }
+ null != b.inputs[e].link && b.disconnectInput(e);
+ var c = this.outputs[a];
+ if (b.onConnectInput && !1 === b.onConnectInput(e, c.type, c)) {
+ return !1;
+ }
+ var g = b.inputs[e];
+ if (k.isValidConnection(c.type, g.type)) {
+ var l = {id:this.graph.last_link_id++, type:g.type, origin_id:this.id, origin_slot:a, target_id:b.id, target_slot:e};
+ this.graph.links[l.id] = l;
+ null == c.links && (c.links = []);
+ c.links.push(l.id);
+ b.inputs[e].link = l.id;
+ this.graph && this.graph._version++;
+ if (this.onConnectionsChange) {
+ this.onConnectionsChange(k.OUTPUT, a, !0, l, c);
+ }
+ if (b.onConnectionsChange) {
+ b.onConnectionsChange(k.INPUT, e, !0, l, g);
+ }
+ if (this.graph && this.graph.onNodeConnectionChange) {
+ this.graph.onNodeConnectionChange(k.OUTPUT, this, a, b, e);
+ }
+ }
+ this.setDirtyCanvas(!1, !0);
+ this.graph.connectionChange(this);
+ return !0;
+ };
+ h.prototype.disconnectOutput = function(a, b) {
+ if (a.constructor === String) {
+ if (a = this.findOutputSlot(a), -1 == a) {
+ return k.debug && console.log("Connect: Error, no slot of name " + a), !1;
+ }
+ } else {
+ if (!this.outputs || a >= this.outputs.length) {
+ return k.debug && console.log("Connect: Error, slot number not found"), !1;
+ }
+ }
+ var e = this.outputs[a];
+ if (!e.links || 0 == e.links.length) {
+ return !1;
+ }
+ if (b) {
+ b.constructor === Number && (b = this.graph.getNodeById(b));
+ if (!b) {
+ throw "Target Node not found";
+ }
+ for (var c = 0, g = e.links.length; c < g; c++) {
+ var l = e.links[c], p = this.graph.links[l];
+ if (p.target_id == b.id) {
+ e.links.splice(c, 1);
+ var d = b.inputs[p.target_slot];
+ d.link = null;
+ delete this.graph.links[l];
+ this.graph && this.graph._version++;
+ if (b.onConnectionsChange) {
+ b.onConnectionsChange(k.INPUT, p.target_slot, !1, p, d);
+ }
+ if (this.onConnectionsChange) {
+ this.onConnectionsChange(k.OUTPUT, a, !1, p, e);
+ }
+ if (this.graph && this.graph.onNodeConnectionChange) {
+ this.graph.onNodeConnectionChange(k.OUTPUT, this, a);
+ }
+ if (this.graph && this.graph.onNodeConnectionChange) {
+ this.graph.onNodeConnectionChange(k.INPUT, b, p.target_slot);
+ }
+ break;
+ }
+ }
+ } else {
+ c = 0;
+ for (g = e.links.length; c < g; c++) {
+ if (l = e.links[c], p = this.graph.links[l]) {
+ b = this.graph.getNodeById(p.target_id);
+ this.graph && this.graph._version++;
+ if (b) {
+ d = b.inputs[p.target_slot];
+ d.link = null;
+ if (b.onConnectionsChange) {
+ b.onConnectionsChange(k.INPUT, p.target_slot, !1, p, d);
+ }
+ if (this.graph && this.graph.onNodeConnectionChange) {
+ this.graph.onNodeConnectionChange(k.INPUT, b, p.target_slot);
+ }
+ }
+ delete this.graph.links[l];
+ if (this.onConnectionsChange) {
+ this.onConnectionsChange(k.OUTPUT, a, !1, p, e);
+ }
+ if (this.graph && this.graph.onNodeConnectionChange) {
+ this.graph.onNodeConnectionChange(k.OUTPUT, this, a);
+ }
+ }
+ }
+ e.links = null;
+ }
+ this.setDirtyCanvas(!1, !0);
+ this.graph.connectionChange(this);
+ return !0;
+ };
+ h.prototype.disconnectInput = function(a) {
+ if (a.constructor === String) {
+ if (a = this.findInputSlot(a), -1 == a) {
+ return k.debug && console.log("Connect: Error, no slot of name " + a), !1;
+ }
+ } else {
+ if (!this.inputs || a >= this.inputs.length) {
+ return k.debug && console.log("Connect: Error, slot number not found"), !1;
+ }
+ }
+ var b = this.inputs[a];
+ if (!b) {
+ return !1;
+ }
+ var e = this.inputs[a].link;
+ this.inputs[a].link = null;
+ var c = this.graph.links[e];
+ if (c) {
+ var g = this.graph.getNodeById(c.origin_id);
+ if (!g) {
+ return !1;
+ }
+ var l = g.outputs[c.origin_slot];
+ if (!l || !l.links || 0 == l.links.length) {
+ return !1;
+ }
+ for (var p = 0, d = l.links.length; p < d; p++) {
+ if (l.links[p] == e) {
+ l.links.splice(p, 1);
+ break;
+ }
+ }
+ delete this.graph.links[e];
+ this.graph && this.graph._version++;
+ if (this.onConnectionsChange) {
+ this.onConnectionsChange(k.INPUT, a, !1, c, b);
+ }
+ if (g.onConnectionsChange) {
+ g.onConnectionsChange(k.OUTPUT, p, !1, c, l);
+ }
+ }
+ this.setDirtyCanvas(!1, !0);
+ this.graph.connectionChange(this);
+ return !0;
+ };
+ h.prototype.getConnectionPos = function(a, b) {
+ return this.flags.collapsed ? a ? [this.pos[0], this.pos[1] - 0.5 * k.NODE_TITLE_HEIGHT] : [this.pos[0] + (this._collapsed_width || k.NODE_COLLAPSED_WIDTH), this.pos[1] - 0.5 * k.NODE_TITLE_HEIGHT] : a && -1 == b ? [this.pos[0] + 10, this.pos[1] + 10] : a && this.inputs.length > b && this.inputs[b].pos ? [this.pos[0] + this.inputs[b].pos[0], this.pos[1] + this.inputs[b].pos[1]] : !a && this.outputs.length > b && this.outputs[b].pos ? [this.pos[0] + this.outputs[b].pos[0], this.pos[1] + this.outputs[b].pos[1]] :
+ this.flags.horizontal ? a ? [this.pos[0] + this.size[0] / this.inputs.length * (b + 0.5), this.pos[1] - k.NODE_TITLE_HEIGHT] : [this.pos[0] + this.size[0] / this.outputs.length * (b + 0.5), this.pos[1] + this.size[1]] : a ? [this.pos[0], this.pos[1] + 10 + b * k.NODE_SLOT_HEIGHT + (this.constructor.slot_start_y || 0)] : [this.pos[0] + this.size[0] + 1, this.pos[1] + 10 + b * k.NODE_SLOT_HEIGHT + (this.constructor.slot_start_y || 0)];
+ };
+ h.prototype.alignToGrid = function() {
+ this.pos[0] = k.CANVAS_GRID_SIZE * Math.round(this.pos[0] / k.CANVAS_GRID_SIZE);
+ this.pos[1] = k.CANVAS_GRID_SIZE * Math.round(this.pos[1] / k.CANVAS_GRID_SIZE);
+ };
+ h.prototype.trace = function(a) {
+ this.console || (this.console = []);
+ this.console.push(a);
+ this.console.length > h.MAX_CONSOLE && this.console.shift();
+ this.graph.onNodeTrace(this, a);
+ };
+ h.prototype.setDirtyCanvas = function(a, b) {
+ this.graph && this.graph.sendActionToCanvas("setDirty", [a, b]);
+ };
+ h.prototype.loadImage = function(a) {
+ var b = new Image;
+ b.src = k.node_images_path + a;
+ b.ready = !1;
+ var e = this;
+ b.onload = function() {
+ this.ready = !0;
+ e.setDirtyCanvas(!0);
+ };
+ return b;
+ };
+ h.prototype.captureInput = function(a) {
+ if (this.graph && this.graph.list_of_graphcanvas) {
+ for (var b = this.graph.list_of_graphcanvas, e = 0; e < b.length; ++e) {
+ var c = b[e];
+ if (a || c.node_capturing_input == this) {
+ c.node_capturing_input = a ? this : null;
+ }
+ }
+ }
+ };
+ h.prototype.collapse = function(a) {
+ this.graph._version++;
+ if (!1 !== this.constructor.collapsable || a) {
+ this.flags.collapsed = this.flags.collapsed ? !1 : !0, this.setDirtyCanvas(!0, !0);
+ }
+ };
+ h.prototype.pin = function(a) {
+ this.graph._version++;
+ this.flags.pinned = void 0 === a ? !this.flags.pinned : a;
+ };
+ h.prototype.localToScreen = function(a, b, e) {
+ return [(a + this.pos[0]) * e.scale + e.offset[0], (b + this.pos[1]) * e.scale + e.offset[1]];
+ };
+ u.LGraphGroup = k.LGraphGroup = n;
+ n.prototype._ctor = function(a) {
+ this.title = a || "Group";
+ this._bounding = new Float32Array([10, 10, 140, 80]);
+ this._pos = this._bounding.subarray(0, 2);
+ this._size = this._bounding.subarray(2, 4);
+ this._nodes = [];
+ this.color = d.node_colors.pale_blue ? d.node_colors.pale_blue.groupcolor : "#AAA";
+ this.graph = null;
+ Object.defineProperty(this, "pos", {set:function(a) {
+ !a || 2 > a.length || (this._pos[0] = a[0], this._pos[1] = a[1]);
+ }, get:function() {
+ return this._pos;
+ }, enumerable:!0});
+ Object.defineProperty(this, "size", {set:function(a) {
+ !a || 2 > a.length || (this._size[0] = Math.max(140, a[0]), this._size[1] = Math.max(80, a[1]));
+ }, get:function() {
+ return this._size;
+ }, enumerable:!0});
+ };
+ n.prototype.configure = function(a) {
+ this.title = a.title;
+ this._bounding.set(a.bounding);
+ this.color = a.color;
+ };
+ n.prototype.serialize = function() {
+ var a = this._bounding;
+ return {title:this.title, bounding:[a[0], a[1], a[2], a[3]], color:this.color};
+ };
+ n.prototype.move = function(a, b, e) {
+ this._pos[0] += a;
+ this._pos[1] += b;
+ if (!e) {
+ for (e = 0; e < this._nodes.length; ++e) {
+ var c = this._nodes[e];
+ c.pos[0] += a;
+ c.pos[1] += b;
+ }
+ }
+ };
+ n.prototype.recomputeInsideNodes = function() {
+ this._nodes.length = 0;
+ for (var a = this.graph._nodes, b = new Float32Array(4), e = 0; e < a.length; ++e) {
+ var c = a[e];
+ c.getBounding(b);
+ v(this._bounding, b) && this._nodes.push(c);
+ }
+ };
+ n.prototype.isPointInside = h.prototype.isPointInside;
+ n.prototype.setDirtyCanvas = h.prototype.setDirtyCanvas;
+ u.LGraphCanvas = k.LGraphCanvas = d;
+ d.link_type_colors = {"-1":"#F85", number:"#AAC", node:"#DCA"};
+ d.gradients = {};
+ d.prototype.clear = function() {
+ this.fps = this.render_time = this.last_draw_time = this.frame = 0;
+ this.scale = 1;
+ this.offset = [0, 0];
+ this.dragging_rectangle = null;
+ this.selected_nodes = {};
+ this.selected_group = null;
+ this.visible_nodes = [];
+ this.connecting_node = this.node_capturing_input = this.node_over = this.node_dragged = null;
+ this.highlighted_links = {};
+ this.dirty_bgcanvas = this.dirty_canvas = !0;
+ this.node_widget = this.node_in_panel = this.dirty_area = null;
+ this.last_mouse = [0, 0];
+ this.last_mouseclick = 0;
+ if (this.onClear) {
+ this.onClear();
+ }
+ };
+ d.prototype.setGraph = function(a, b) {
+ this.graph != a && (b || this.clear(), !a && this.graph ? this.graph.detachCanvas(this) : (a.attachCanvas(this), this.setDirty(!0, !0)));
+ };
+ d.prototype.openSubgraph = function(a) {
+ if (!a) {
+ throw "graph cannot be null";
+ }
+ if (this.graph == a) {
+ throw "graph cannot be the same";
+ }
+ this.clear();
+ this.graph && (this._graph_stack || (this._graph_stack = []), this._graph_stack.push(this.graph));
+ a.attachCanvas(this);
+ this.setDirty(!0, !0);
+ };
+ d.prototype.closeSubgraph = function() {
+ if (this._graph_stack && 0 != this._graph_stack.length) {
+ var a = this._graph_stack.pop();
+ this.selected_nodes = {};
+ this.highlighted_links = {};
+ a.attachCanvas(this);
+ this.setDirty(!0, !0);
+ }
+ };
+ d.prototype.setCanvas = function(a, b) {
+ if (a && a.constructor === String && (a = document.getElementById(a), !a)) {
+ throw "Error creating LiteGraph canvas: Canvas not found";
+ }
+ if (a !== this.canvas && (!a && this.canvas && (b || this.unbindEvents()), this.canvas = a)) {
+ a.className += " lgraphcanvas";
+ a.data = this;
+ this.bgcanvas = null;
+ this.bgcanvas || (this.bgcanvas = document.createElement("canvas"), this.bgcanvas.width = this.canvas.width, this.bgcanvas.height = this.canvas.height);
+ if (null == a.getContext) {
+ if ("canvas" != a.localName) {
+ throw "Element supplied for LGraphCanvas must be a