diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..60cb91d05 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,9 @@ +# Contribution Rules +There are some simple rules that everyone should follow: + +### Do not commit files from bulid folder +> I usually have horrible merge conflicts when I upload the build version that take me too much time to solve, but I want to keep the build version in the repo, so I guess it would be better if only one of us does the built, which would be me. +> https://github.com/jagenjo/litegraph.js/pull/155#issuecomment-656602861 +Those files will be updated by owner. + + diff --git a/README.md b/README.md index 782a0bbe2..e34d7bef1 100755 --- a/README.md +++ b/README.md @@ -1,15 +1,15 @@ # litegraph.js -A library in Javascript to create graphs in the browser similar to Unreal Blueprints. Nodes can be programmed easily and it includes an editor to construct the graphs. +A library in Javascript to create graphs in the browser similar to Unreal Blueprints. Nodes can be programmed easily and it includes an editor to construct and tests the graphs. It can be integrated easily in any existing web applications and graphs can be run without the need of the editor. -Try it in the [demo site](https://tamats.com/projects/litegraph/demo). +Try it in the [demo site](https://tamats.com/projects/litegraph/editor). ![Node Graph](imgs/node_graph_example.png "WebGLStudio") ## Features -- Renders on Canvas2D (zoom in, zoom out, panning, can be used inside a WebGLTexture) +- Renders on Canvas2D (zoom in/out and panning, easy to render complex interfaces, 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) @@ -19,6 +19,7 @@ Try it in the [demo site](https://tamats.com/projects/litegraph/demo). - Graphs can be executed in NodeJS - Highly customizable nodes (color, shape, slots vertical or horizontal, widgets, custom rendering) - Easy to integrate in any JS application (one single file, no dependencies) +- Typescript support ## Nodes provided Although it is easy to create new node types, LiteGraph comes with some default nodes that could be useful for many cases: @@ -35,7 +36,7 @@ You can install it using npm npm install litegraph.js ``` -Or downloading the ```build/litegraph.js``` version from this repository. +Or downloading the ```build/litegraph.js``` and ```css/litegraph.css``` version from this repository. ## First project ## @@ -179,6 +180,8 @@ You can write any feedback to javi.agenjo@gmail.com - rappestad - InventivetalentDev - NateScarlet +- coderofsalvation +- ilyabesk diff --git a/build/litegraph.js b/build/litegraph.js deleted file mode 100644 index 45b7ea22c..000000000 --- a/build/litegraph.js +++ /dev/null @@ -1,24065 +0,0 @@ -(function(global) { - // ************************************************************* - // LiteGraph CLASS ******* - // ************************************************************* - - /** - * The Global Scope. It contains all the registered node classes. - * - * @class LiteGraph - * @constructor - */ - - var LiteGraph = (global.LiteGraph = { - VERSION: 0.4, - - CANVAS_GRID_SIZE: 10, - - NODE_TITLE_HEIGHT: 30, - NODE_TITLE_TEXT_Y: 20, - NODE_SLOT_HEIGHT: 20, - 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: "#353535", - NODE_DEFAULT_BOXCOLOR: "#666", - NODE_DEFAULT_SHAPE: "box", - DEFAULT_SHADOW_COLOR: "rgba(0,0,0,0.5)", - DEFAULT_GROUP_FONT: 24, - - WIDGET_BGCOLOR: "#222", - WIDGET_OUTLINE_COLOR: "#666", - WIDGET_TEXT_COLOR: "#DDD", - WIDGET_SECONDARY_TEXT_COLOR: "#999", - - LINK_COLOR: "#9A9", - EVENT_LINK_COLOR: "#A86", - CONNECTING_LINK_COLOR: "#AFA", - - MAX_NUMBER_OF_NODES: 1000, //avoid infinite loops - DEFAULT_POSITION: [100, 100], //default node position - VALID_SHAPES: ["default", "box", "round", "card"], //,"circle" - - //shapes are used for nodes but also for slots - BOX_SHAPE: 1, - ROUND_SHAPE: 2, - CIRCLE_SHAPE: 3, - CARD_SHAPE: 4, - ARROW_SHAPE: 5, - - //enums - INPUT: 1, - OUTPUT: 2, - - EVENT: -1, //for outputs - ACTION: -1, //for inputs - - ALWAYS: 0, - ON_EVENT: 1, - NEVER: 2, - ON_TRIGGER: 3, - - UP: 1, - DOWN: 2, - LEFT: 3, - RIGHT: 4, - CENTER: 5, - - STRAIGHT_LINK: 0, - LINEAR_LINK: 1, - SPLINE_LINK: 2, - - NORMAL_TITLE: 0, - NO_TITLE: 1, - TRANSPARENT_TITLE: 2, - AUTOHIDE_TITLE: 3, - - proxy: null, //used to redirect calls - node_images_path: "", - - debug: false, - catch_exceptions: true, - throw_errors: true, - allow_scripts: false, //if set to true some nodes like Formula would be allowed to evaluate code that comes from unsafe sources (like node configuration), which could lead to exploits - registered_node_types: {}, //nodetypes by string - node_types_by_file_extension: {}, //used for dropping files in the canvas - Nodes: {}, //node types by classname - - searchbox_extras: {}, //used to add extra features to the search box - - /** - * Register a node class so it can be listed when the user wants to create a new one - * @method registerNodeType - * @param {String} type name of the node and path - * @param {Class} base_class class containing the structure of a node - */ - - registerNodeType: function(type, base_class) { - if (!base_class.prototype) { - throw "Cannot register a simple object, it must be a class with a prototype"; - } - base_class.type = type; - - if (LiteGraph.debug) { - console.log("Node registered: " + type); - } - - var categories = type.split("/"); - var classname = base_class.name; - - var pos = type.lastIndexOf("/"); - base_class.category = type.substr(0, pos); - - if (!base_class.title) { - base_class.title = classname; - } - //info.name = name.substr(pos+1,name.length - pos); - - //extend class - if (base_class.prototype) { - //is a class - for (var i in LGraphNode.prototype) { - if (!base_class.prototype[i]) { - base_class.prototype[i] = LGraphNode.prototype[i]; - } - } - } - - if( !Object.hasOwnProperty( base_class.prototype, "shape") ) - { - Object.defineProperty(base_class.prototype, "shape", { - set: function(v) { - switch (v) { - case "default": - delete this._shape; - break; - case "box": - this._shape = LiteGraph.BOX_SHAPE; - break; - case "round": - this._shape = LiteGraph.ROUND_SHAPE; - break; - case "circle": - this._shape = LiteGraph.CIRCLE_SHAPE; - break; - case "card": - this._shape = LiteGraph.CARD_SHAPE; - break; - default: - this._shape = v; - } - }, - get: function(v) { - return this._shape; - }, - enumerable: true - }); - } - - var prev = this.registered_node_types[type]; - if(prev) - console.log("replacing node type: " + type); - - this.registered_node_types[type] = base_class; - if (base_class.constructor.name) { - this.Nodes[classname] = base_class; - } - if (LiteGraph.onNodeTypeRegistered) { - LiteGraph.onNodeTypeRegistered(type, base_class); - } - if (prev && LiteGraph.onNodeTypeReplaced) { - LiteGraph.onNodeTypeReplaced(type, base_class, prev); - } - - //warnings - if (base_class.prototype.onPropertyChange) { - console.warn( - "LiteGraph node class " + - type + - " has onPropertyChange method, it must be called onPropertyChanged with d at the end" - ); - } - - //used to know which nodes create when dragging files to the canvas - if (base_class.supported_extensions) { - for (var i=0; i < base_class.supported_extensions.length; i++) { - var ext = base_class.supported_extensions[i]; - if(ext && ext.constructor === String) - this.node_types_by_file_extension[ ext.toLowerCase() ] = base_class; - } - } - }, - - /** - * Create a new nodetype by passing a function, it wraps it with a proper class and generates inputs according to the parameters of the function. - * Useful to wrap simple methods that do not require properties, and that only process some input to generate an output. - * @method wrapFunctionAsNode - * @param {String} name node name with namespace (p.e.: 'math/sum') - * @param {Function} func - * @param {Array} param_types [optional] an array containing the type of every parameter, otherwise parameters will accept any type - * @param {String} return_type [optional] string with the return type, otherwise it will be generic - * @param {Object} properties [optional] properties to be configurable - */ - wrapFunctionAsNode: function( - name, - func, - param_types, - return_type, - properties - ) { - var params = Array(func.length); - var code = ""; - var names = LiteGraph.getParameterNames(func); - for (var i = 0; i < names.length; ++i) { - code += - "this.addInput('" + - names[i] + - "'," + - (param_types && param_types[i] - ? "'" + param_types[i] + "'" - : "0") + - ");\n"; - } - code += - "this.addOutput('out'," + - (return_type ? "'" + return_type + "'" : 0) + - ");\n"; - if (properties) { - code += - "this.properties = " + JSON.stringify(properties) + ";\n"; - } - var classobj = Function(code); - classobj.title = name.split("/").pop(); - classobj.desc = "Generated from " + func.name; - classobj.prototype.onExecute = function onExecute() { - for (var i = 0; i < params.length; ++i) { - params[i] = this.getInputData(i); - } - var r = func.apply(this, params); - this.setOutputData(0, r); - }; - this.registerNodeType(name, classobj); - }, - - /** - * Adds this method to all nodetypes, existing and to be created - * (You can add it to LGraphNode.prototype but then existing node types wont have it) - * @method addNodeMethod - * @param {Function} func - */ - addNodeMethod: function(name, func) { - LGraphNode.prototype[name] = func; - for (var i in this.registered_node_types) { - var type = this.registered_node_types[i]; - if (type.prototype[name]) { - type.prototype["_" + name] = type.prototype[name]; - } //keep old in case of replacing - type.prototype[name] = func; - } - }, - - /** - * Create a node of a given type with a name. The node is not attached to any graph yet. - * @method createNode - * @param {String} type full name of the node class. p.e. "math/sin" - * @param {String} name a name to distinguish from other nodes - * @param {Object} options to set options - */ - - createNode: function(type, title, options) { - var base_class = this.registered_node_types[type]; - if (!base_class) { - if (LiteGraph.debug) { - console.log( - 'GraphNode type "' + type + '" not registered.' - ); - } - return null; - } - - var prototype = base_class.prototype || base_class; - - title = title || base_class.title || type; - - var node = null; - - if (LiteGraph.catch_exceptions) { - try { - node = new base_class(title); - } catch (err) { - console.error(err); - return null; - } - } else { - node = new base_class(title); - } - - node.type = type; - - if (!node.title && title) { - node.title = title; - } - if (!node.properties) { - node.properties = {}; - } - if (!node.properties_info) { - node.properties_info = []; - } - if (!node.flags) { - node.flags = {}; - } - if (!node.size) { - node.size = node.computeSize(); - } - if (!node.pos) { - node.pos = LiteGraph.DEFAULT_POSITION.concat(); - } - if (!node.mode) { - node.mode = LiteGraph.ALWAYS; - } - - //extra options - if (options) { - for (var i in options) { - node[i] = options[i]; - } - } - - return node; - }, - - /** - * Returns a registered node type with a given name - * @method getNodeType - * @param {String} type full name of the node class. p.e. "math/sin" - * @return {Class} the node class - */ - getNodeType: function(type) { - return this.registered_node_types[type]; - }, - - /** - * Returns a list of node types matching one category - * @method getNodeType - * @param {String} category category name - * @return {Array} array with all the node classes - */ - - getNodeTypesInCategory: function(category, filter) { - var r = []; - for (var i in this.registered_node_types) { - var type = this.registered_node_types[i]; - if (filter && type.filter && type.filter != filter) { - continue; - } - - if (category == "") { - if (type.category == null) { - r.push(type); - } - } else if (type.category == category) { - r.push(type); - } - } - - return r; - }, - - /** - * Returns a list with all the node type categories - * @method getNodeTypesCategories - * @return {Array} array with all the names of the categories - */ - - getNodeTypesCategories: function( filter ) { - var categories = { "": 1 }; - for (var i in this.registered_node_types) { - var type = this.registered_node_types[i]; - if ( type.category && !type.skip_list ) - { - if(filter && type.filter != filter) - continue; - categories[type.category] = 1; - } - } - var result = []; - for (var i in categories) { - result.push(i); - } - return result; - }, - - //debug purposes: reloads all the js scripts that matches a wildcard - reloadNodes: function(folder_wildcard) { - var tmp = document.getElementsByTagName("script"); - //weird, this array changes by its own, so we use a copy - var script_files = []; - for (var i=0; i < tmp.length; i++) { - script_files.push(tmp[i]); - } - - var docHeadObj = document.getElementsByTagName("head")[0]; - folder_wildcard = document.location.href + folder_wildcard; - - for (var i=0; i < script_files.length; i++) { - var src = script_files[i].src; - if ( - !src || - src.substr(0, folder_wildcard.length) != folder_wildcard - ) { - continue; - } - - try { - if (LiteGraph.debug) { - console.log("Reloading: " + src); - } - var dynamicScript = document.createElement("script"); - dynamicScript.type = "text/javascript"; - dynamicScript.src = src; - docHeadObj.appendChild(dynamicScript); - docHeadObj.removeChild(script_files[i]); - } catch (err) { - if (LiteGraph.throw_errors) { - throw err; - } - if (LiteGraph.debug) { - console.log("Error while reloading " + src); - } - } - } - - if (LiteGraph.debug) { - console.log("Nodes reloaded"); - } - }, - - //separated just to improve if it doesn't work - cloneObject: function(obj, target) { - if (obj == null) { - return null; - } - var r = JSON.parse(JSON.stringify(obj)); - if (!target) { - return r; - } - - for (var i in r) { - target[i] = r[i]; - } - return target; - }, - - isValidConnection: function(type_a, type_b) { - if ( - !type_a || //generic output - !type_b || //generic input - type_a == type_b || //same type (is valid for triggers) - (type_a == LiteGraph.EVENT && type_b == LiteGraph.ACTION) - ) { - return true; - } - - // Enforce string type to handle toLowerCase call (-1 number not ok) - type_a = String(type_a); - type_b = String(type_b); - type_a = type_a.toLowerCase(); - type_b = type_b.toLowerCase(); - - // For nodes supporting multiple connection types - if (type_a.indexOf(",") == -1 && type_b.indexOf(",") == -1) { - return type_a == type_b; - } - - // Check all permutations to see if one is valid - var supported_types_a = type_a.split(","); - var supported_types_b = type_b.split(","); - for (var i = 0; i < supported_types_a.length; ++i) { - for (var j = 0; j < supported_types_b.length; ++j) { - if (supported_types_a[i] == supported_types_b[j]) { - return true; - } - } - } - - return false; - }, - - registerSearchboxExtra: function(node_type, description, data) { - this.searchbox_extras[description.toLowerCase()] = { - type: node_type, - desc: description, - data: data - }; - } - }); - - //timer that works everywhere - if (typeof performance != "undefined") { - LiteGraph.getTime = performance.now.bind(performance); - } else if (typeof Date != "undefined" && Date.now) { - LiteGraph.getTime = Date.now.bind(Date); - } else if (typeof process != "undefined") { - LiteGraph.getTime = function() { - var t = process.hrtime(); - return t[0] * 0.001 + t[1] * 1e-6; - }; - } else { - LiteGraph.getTime = function getTime() { - return new Date().getTime(); - }; - } - - //********************************************************************************* - // LGraph CLASS - //********************************************************************************* - - /** - * LGraph is the class that contain a full graph. We instantiate one and add nodes to it, and then we can run the execution loop. - * - * @class LGraph - * @constructor - * @param {Object} o data from previous serialization [optional] - */ - - function LGraph(o) { - if (LiteGraph.debug) { - console.log("Graph created"); - } - this.list_of_graphcanvas = null; - this.clear(); - - if (o) { - this.configure(o); - } - } - - global.LGraph = LiteGraph.LGraph = LGraph; - - //default supported types - LGraph.supported_types = ["number", "string", "boolean"]; - - //used to know which types of connections support this graph (some graphs do not allow certain types) - LGraph.prototype.getSupportedTypes = function() { - return this.supported_types || LGraph.supported_types; - }; - - LGraph.STATUS_STOPPED = 1; - LGraph.STATUS_RUNNING = 2; - - /** - * Removes all nodes from this graph - * @method clear - */ - - LGraph.prototype.clear = function() { - this.stop(); - this.status = LGraph.STATUS_STOPPED; - - this.last_node_id = 0; - this.last_link_id = 0; - - this._version = -1; //used to detect changes - - //safe clear - if (this._nodes) { - for (var i = 0; i < this._nodes.length; ++i) { - var node = this._nodes[i]; - if (node.onRemoved) { - node.onRemoved(); - } - } - } - - //nodes - this._nodes = []; - this._nodes_by_id = {}; - this._nodes_in_order = []; //nodes that are executable sorted in execution order - this._nodes_executable = null; //nodes that contain onExecute - - //other scene stuff - this._groups = []; - - //links - this.links = {}; //container with all the links - - //iterations - this.iteration = 0; - - //custom data - this.config = {}; - this.vars = {}; - - //timing - this.globaltime = 0; - this.runningtime = 0; - this.fixedtime = 0; - this.fixedtime_lapse = 0.01; - this.elapsed_time = 0.01; - this.last_update_time = 0; - this.starttime = 0; - - this.catch_errors = true; - - //subgraph_data - this.inputs = {}; - this.outputs = {}; - - //notify canvas to redraw - this.change(); - - this.sendActionToCanvas("clear"); - }; - - /** - * Attach Canvas to this graph - * @method attachCanvas - * @param {GraphCanvas} graph_canvas - */ - - LGraph.prototype.attachCanvas = function(graphcanvas) { - if (graphcanvas.constructor != LGraphCanvas) { - throw "attachCanvas expects a LGraphCanvas instance"; - } - if (graphcanvas.graph && graphcanvas.graph != this) { - graphcanvas.graph.detachCanvas(graphcanvas); - } - - graphcanvas.graph = this; - if (!this.list_of_graphcanvas) { - this.list_of_graphcanvas = []; - } - this.list_of_graphcanvas.push(graphcanvas); - }; - - /** - * Detach Canvas from this graph - * @method detachCanvas - * @param {GraphCanvas} graph_canvas - */ - LGraph.prototype.detachCanvas = function(graphcanvas) { - if (!this.list_of_graphcanvas) { - return; - } - - var pos = this.list_of_graphcanvas.indexOf(graphcanvas); - if (pos == -1) { - return; - } - graphcanvas.graph = null; - this.list_of_graphcanvas.splice(pos, 1); - }; - - /** - * Starts running this graph every interval milliseconds. - * @method start - * @param {number} interval amount of milliseconds between executions, if 0 then it renders to the monitor refresh rate - */ - - LGraph.prototype.start = function(interval) { - if (this.status == LGraph.STATUS_RUNNING) { - return; - } - this.status = LGraph.STATUS_RUNNING; - - if (this.onPlayEvent) { - this.onPlayEvent(); - } - - this.sendEventToAllNodes("onStart"); - - //launch - this.starttime = LiteGraph.getTime(); - this.last_update_time = this.starttime; - interval = interval || 0; - var that = this; - - if ( - interval == 0 && - typeof window != "undefined" && - window.requestAnimationFrame - ) { - function on_frame() { - if (that.execution_timer_id != -1) { - return; - } - window.requestAnimationFrame(on_frame); - that.runStep(1, !this.catch_errors); - } - this.execution_timer_id = -1; - on_frame(); - } else { - this.execution_timer_id = setInterval(function() { - //execute - that.runStep(1, !this.catch_errors); - }, interval); - } - }; - - /** - * Stops the execution loop of the graph - * @method stop execution - */ - - LGraph.prototype.stop = function() { - if (this.status == LGraph.STATUS_STOPPED) { - return; - } - - this.status = LGraph.STATUS_STOPPED; - - if (this.onStopEvent) { - this.onStopEvent(); - } - - if (this.execution_timer_id != null) { - if (this.execution_timer_id != -1) { - clearInterval(this.execution_timer_id); - } - this.execution_timer_id = null; - } - - this.sendEventToAllNodes("onStop"); - }; - - /** - * Run N steps (cycles) of the graph - * @method runStep - * @param {number} num number of steps to run, default is 1 - * @param {Boolean} do_not_catch_errors [optional] if you want to try/catch errors - * @param {number} limit max number of nodes to execute (used to execute from start to a node) - */ - - LGraph.prototype.runStep = function(num, do_not_catch_errors, limit ) { - num = num || 1; - - var start = LiteGraph.getTime(); - this.globaltime = 0.001 * (start - this.starttime); - - var nodes = this._nodes_executable - ? this._nodes_executable - : this._nodes; - if (!nodes) { - return; - } - - limit = limit || nodes.length; - - if (do_not_catch_errors) { - //iterations - for (var i = 0; i < num; i++) { - for (var j = 0; j < limit; ++j) { - var node = nodes[j]; - if (node.mode == LiteGraph.ALWAYS && node.onExecute) { - node.onExecute(); //hard to send elapsed time - } - } - - this.fixedtime += this.fixedtime_lapse; - if (this.onExecuteStep) { - this.onExecuteStep(); - } - } - - if (this.onAfterExecute) { - this.onAfterExecute(); - } - } else { - try { - //iterations - for (var i = 0; i < num; i++) { - for (var j = 0; j < limit; ++j) { - var node = nodes[j]; - if (node.mode == LiteGraph.ALWAYS && node.onExecute) { - node.onExecute(); - } - } - - this.fixedtime += this.fixedtime_lapse; - if (this.onExecuteStep) { - this.onExecuteStep(); - } - } - - if (this.onAfterExecute) { - this.onAfterExecute(); - } - this.errors_in_execution = false; - } catch (err) { - this.errors_in_execution = true; - if (LiteGraph.throw_errors) { - throw err; - } - if (LiteGraph.debug) { - console.log("Error during execution: " + err); - } - this.stop(); - } - } - - var now = LiteGraph.getTime(); - var elapsed = now - start; - if (elapsed == 0) { - elapsed = 1; - } - this.execution_time = 0.001 * elapsed; - this.globaltime += 0.001 * elapsed; - this.iteration += 1; - this.elapsed_time = (now - this.last_update_time) * 0.001; - this.last_update_time = now; - }; - - /** - * Updates the graph execution order according to relevance of the nodes (nodes with only outputs have more relevance than - * nodes with only inputs. - * @method updateExecutionOrder - */ - LGraph.prototype.updateExecutionOrder = function() { - this._nodes_in_order = this.computeExecutionOrder(false); - this._nodes_executable = []; - for (var i = 0; i < this._nodes_in_order.length; ++i) { - if (this._nodes_in_order[i].onExecute) { - this._nodes_executable.push(this._nodes_in_order[i]); - } - } - }; - - //This is more internal, it computes the executable nodes in order and returns it - LGraph.prototype.computeExecutionOrder = function( - only_onExecute, - set_level - ) { - var L = []; - var S = []; - var M = {}; - var visited_links = {}; //to avoid repeating links - var remaining_links = {}; //to a - - //search for the nodes without inputs (starting nodes) - for (var i = 0, l = this._nodes.length; i < l; ++i) { - var node = this._nodes[i]; - if (only_onExecute && !node.onExecute) { - continue; - } - - M[node.id] = node; //add to pending nodes - - var num = 0; //num of input connections - if (node.inputs) { - for (var j = 0, l2 = node.inputs.length; j < l2; j++) { - if (node.inputs[j] && node.inputs[j].link != null) { - num += 1; - } - } - } - - if (num == 0) { - //is a starting node - S.push(node); - if (set_level) { - node._level = 1; - } - } //num of input links - else { - if (set_level) { - node._level = 0; - } - remaining_links[node.id] = num; - } - } - - while (true) { - if (S.length == 0) { - break; - } - - //get an starting node - var node = S.shift(); - L.push(node); //add to ordered list - delete M[node.id]; //remove from the pending nodes - - if (!node.outputs) { - continue; - } - - //for every output - for (var i = 0; i < node.outputs.length; i++) { - var output = node.outputs[i]; - //not connected - if ( - output == null || - output.links == null || - output.links.length == 0 - ) { - continue; - } - - //for every connection - for (var j = 0; j < output.links.length; j++) { - var link_id = output.links[j]; - var link = this.links[link_id]; - if (!link) { - continue; - } - - //already visited link (ignore it) - if (visited_links[link.id]) { - continue; - } - - var target_node = this.getNodeById(link.target_id); - if (target_node == null) { - visited_links[link.id] = true; - continue; - } - - if ( - set_level && - (!target_node._level || - target_node._level <= node._level) - ) { - target_node._level = node._level + 1; - } - - visited_links[link.id] = true; //mark as visited - remaining_links[target_node.id] -= 1; //reduce the number of links remaining - if (remaining_links[target_node.id] == 0) { - S.push(target_node); - } //if no more links, then add to starters array - } - } - } - - //the remaining ones (loops) - for (var i in M) { - L.push(M[i]); - } - - if (L.length != this._nodes.length && LiteGraph.debug) { - console.warn("something went wrong, nodes missing"); - } - - var l = L.length; - - //save order number in the node - for (var i = 0; i < l; ++i) { - L[i].order = i; - } - - //sort now by priority - L = L.sort(function(A, B) { - var Ap = A.constructor.priority || A.priority || 0; - var Bp = B.constructor.priority || B.priority || 0; - if (Ap == Bp) { - //if same priority, sort by order - return A.order - B.order; - } - return Ap - Bp; //sort by priority - }); - - //save order number in the node, again... - for (var i = 0; i < l; ++i) { - L[i].order = i; - } - - return L; - }; - - /** - * Returns all the nodes that could affect this one (ancestors) by crawling all the inputs recursively. - * It doesn't include the node itself - * @method getAncestors - * @return {Array} an array with all the LGraphNodes that affect this node, in order of execution - */ - LGraph.prototype.getAncestors = function(node) { - var ancestors = []; - var pending = [node]; - var visited = {}; - - while (pending.length) { - var current = pending.shift(); - if (!current.inputs) { - continue; - } - if (!visited[current.id] && current != node) { - visited[current.id] = true; - ancestors.push(current); - } - - for (var i = 0; i < current.inputs.length; ++i) { - var input = current.getInputNode(i); - if (input && ancestors.indexOf(input) == -1) { - pending.push(input); - } - } - } - - ancestors.sort(function(a, b) { - return a.order - b.order; - }); - return ancestors; - }; - - /** - * Positions every node in a more readable manner - * @method arrange - */ - LGraph.prototype.arrange = function(margin) { - margin = margin || 100; - - var nodes = this.computeExecutionOrder(false, true); - var columns = []; - for (var i = 0; i < nodes.length; ++i) { - var node = nodes[i]; - var col = node._level || 1; - if (!columns[col]) { - columns[col] = []; - } - columns[col].push(node); - } - - var x = margin; - - for (var i = 0; i < columns.length; ++i) { - var column = columns[i]; - if (!column) { - continue; - } - var max_size = 100; - var y = margin + LiteGraph.NODE_TITLE_HEIGHT; - for (var j = 0; j < column.length; ++j) { - var node = column[j]; - node.pos[0] = x; - node.pos[1] = y; - if (node.size[0] > max_size) { - max_size = node.size[0]; - } - y += node.size[1] + margin + LiteGraph.NODE_TITLE_HEIGHT; - } - x += max_size + margin; - } - - this.setDirtyCanvas(true, true); - }; - - /** - * Returns the amount of time the graph has been running in milliseconds - * @method getTime - * @return {number} number of milliseconds the graph has been running - */ - LGraph.prototype.getTime = function() { - return this.globaltime; - }; - - /** - * Returns the amount of time accumulated using the fixedtime_lapse var. This is used in context where the time increments should be constant - * @method getFixedTime - * @return {number} number of milliseconds the graph has been running - */ - - LGraph.prototype.getFixedTime = function() { - return this.fixedtime; - }; - - /** - * Returns the amount of time it took to compute the latest iteration. Take into account that this number could be not correct - * if the nodes are using graphical actions - * @method getElapsedTime - * @return {number} number of milliseconds it took the last cycle - */ - - LGraph.prototype.getElapsedTime = function() { - return this.elapsed_time; - }; - - /** - * Sends an event to all the nodes, useful to trigger stuff - * @method sendEventToAllNodes - * @param {String} eventname the name of the event (function to be called) - * @param {Array} params parameters in array format - */ - LGraph.prototype.sendEventToAllNodes = function(eventname, params, mode) { - mode = mode || LiteGraph.ALWAYS; - - var nodes = this._nodes_in_order ? this._nodes_in_order : this._nodes; - if (!nodes) { - return; - } - - for (var j = 0, l = nodes.length; j < l; ++j) { - var node = nodes[j]; - - if ( - node.constructor === LiteGraph.Subgraph && - eventname != "onExecute" - ) { - if (node.mode == mode) { - node.sendEventToAllNodes(eventname, params, mode); - } - continue; - } - - if (!node[eventname] || node.mode != mode) { - continue; - } - if (params === undefined) { - node[eventname](); - } else if (params && params.constructor === Array) { - node[eventname].apply(node, params); - } else { - node[eventname](params); - } - } - }; - - LGraph.prototype.sendActionToCanvas = function(action, params) { - if (!this.list_of_graphcanvas) { - return; - } - - for (var i = 0; i < this.list_of_graphcanvas.length; ++i) { - var c = this.list_of_graphcanvas[i]; - if (c[action]) { - c[action].apply(c, params); - } - } - }; - - /** - * Adds a new node instance to this graph - * @method add - * @param {LGraphNode} node the instance of the node - */ - - LGraph.prototype.add = function(node, skip_compute_order) { - if (!node) { - return; - } - - //groups - if (node.constructor === LGraphGroup) { - this._groups.push(node); - this.setDirtyCanvas(true); - this.change(); - node.graph = this; - this._version++; - return; - } - - //nodes - if (node.id != -1 && this._nodes_by_id[node.id] != null) { - console.warn( - "LiteGraph: there is already a node with this ID, changing it" - ); - node.id = ++this.last_node_id; - } - - if (this._nodes.length >= LiteGraph.MAX_NUMBER_OF_NODES) { - throw "LiteGraph: max number of nodes in a graph reached"; - } - - //give him an id - if (node.id == null || node.id == -1) { - node.id = ++this.last_node_id; - } else if (this.last_node_id < node.id) { - this.last_node_id = node.id; - } - - node.graph = this; - this._version++; - - this._nodes.push(node); - this._nodes_by_id[node.id] = node; - - if (node.onAdded) { - node.onAdded(this); - } - - if (this.config.align_to_grid) { - node.alignToGrid(); - } - - if (!skip_compute_order) { - this.updateExecutionOrder(); - } - - if (this.onNodeAdded) { - this.onNodeAdded(node); - } - - this.setDirtyCanvas(true); - this.change(); - - return node; //to chain actions - }; - - /** - * Removes a node from the graph - * @method remove - * @param {LGraphNode} node the instance of the node - */ - - LGraph.prototype.remove = function(node) { - if (node.constructor === LiteGraph.LGraphGroup) { - var index = this._groups.indexOf(node); - if (index != -1) { - this._groups.splice(index, 1); - } - node.graph = null; - this._version++; - this.setDirtyCanvas(true, true); - this.change(); - return; - } - - if (this._nodes_by_id[node.id] == null) { - return; - } //not found - - if (node.ignore_remove) { - return; - } //cannot be removed - - //disconnect inputs - if (node.inputs) { - for (var i = 0; i < node.inputs.length; i++) { - var slot = node.inputs[i]; - if (slot.link != null) { - node.disconnectInput(i); - } - } - } - - //disconnect outputs - if (node.outputs) { - for (var i = 0; i < node.outputs.length; i++) { - var slot = node.outputs[i]; - if (slot.links != null && slot.links.length) { - node.disconnectOutput(i); - } - } - } - - //node.id = -1; //why? - - //callback - if (node.onRemoved) { - node.onRemoved(); - } - - node.graph = null; - this._version++; - - //remove from canvas render - if (this.list_of_graphcanvas) { - for (var i = 0; i < this.list_of_graphcanvas.length; ++i) { - var canvas = this.list_of_graphcanvas[i]; - if (canvas.selected_nodes[node.id]) { - delete canvas.selected_nodes[node.id]; - } - if (canvas.node_dragged == node) { - canvas.node_dragged = null; - } - } - } - - //remove from containers - var pos = this._nodes.indexOf(node); - if (pos != -1) { - this._nodes.splice(pos, 1); - } - delete this._nodes_by_id[node.id]; - - if (this.onNodeRemoved) { - this.onNodeRemoved(node); - } - - this.setDirtyCanvas(true, true); - this.change(); - - this.updateExecutionOrder(); - }; - - /** - * Returns a node by its id. - * @method getNodeById - * @param {Number} id - */ - - LGraph.prototype.getNodeById = function(id) { - if (id == null) { - return null; - } - return this._nodes_by_id[id]; - }; - - /** - * Returns a list of nodes that matches a class - * @method findNodesByClass - * @param {Class} classObject the class itself (not an string) - * @return {Array} a list with all the nodes of this type - */ - LGraph.prototype.findNodesByClass = function(classObject, result) { - result = result || []; - result.length = 0; - for (var i = 0, l = this._nodes.length; i < l; ++i) { - if (this._nodes[i].constructor === classObject) { - result.push(this._nodes[i]); - } - } - return result; - }; - - /** - * Returns a list of nodes that matches a type - * @method findNodesByType - * @param {String} type the name of the node type - * @return {Array} a list with all the nodes of this type - */ - LGraph.prototype.findNodesByType = function(type, result) { - var type = type.toLowerCase(); - result = result || []; - result.length = 0; - for (var i = 0, l = this._nodes.length; i < l; ++i) { - if (this._nodes[i].type.toLowerCase() == type) { - result.push(this._nodes[i]); - } - } - return result; - }; - - /** - * Returns the first node that matches a name in its title - * @method findNodeByTitle - * @param {String} name the name of the node to search - * @return {Node} the node or null - */ - LGraph.prototype.findNodeByTitle = function(title) { - for (var i = 0, l = this._nodes.length; i < l; ++i) { - if (this._nodes[i].title == title) { - return this._nodes[i]; - } - } - return null; - }; - - /** - * Returns a list of nodes that matches a name - * @method findNodesByTitle - * @param {String} name the name of the node to search - * @return {Array} a list with all the nodes with this name - */ - LGraph.prototype.findNodesByTitle = function(title) { - var result = []; - for (var i = 0, l = this._nodes.length; i < l; ++i) { - if (this._nodes[i].title == title) { - result.push(this._nodes[i]); - } - } - return result; - }; - - /** - * Returns the top-most node in this position of the canvas - * @method getNodeOnPos - * @param {number} x the x coordinate in canvas space - * @param {number} y the y coordinate in canvas space - * @param {Array} nodes_list a list with all the nodes to search from, by default is all the nodes in the graph - * @return {LGraphNode} the node at this position or null - */ - LGraph.prototype.getNodeOnPos = function(x, y, nodes_list, margin) { - nodes_list = nodes_list || this._nodes; - for (var i = nodes_list.length - 1; i >= 0; i--) { - var n = nodes_list[i]; - if (n.isPointInside(x, y, margin)) { - return n; - } - } - return null; - }; - - /** - * Returns the top-most group in that position - * @method getGroupOnPos - * @param {number} x the x coordinate in canvas space - * @param {number} y the y coordinate in canvas space - * @return {LGraphGroup} the group or null - */ - LGraph.prototype.getGroupOnPos = function(x, y) { - for (var i = this._groups.length - 1; i >= 0; i--) { - var g = this._groups[i]; - if (g.isPointInside(x, y, 2, true)) { - return g; - } - } - return null; - }; - - /** - * Checks that the node type matches the node type registered, used when replacing a nodetype by a newer version during execution - * this replaces the ones using the old version with the new version - * @method checkNodeTypes - */ - LGraph.prototype.checkNodeTypes = function() { - var changes = false; - for (var i = 0; i < this._nodes.length; i++) { - var node = this._nodes[i]; - var ctor = LiteGraph.registered_node_types[node.type]; - if (node.constructor == ctor) { - continue; - } - console.log("node being replaced by newer version: " + node.type); - var newnode = LiteGraph.createNode(node.type); - changes = true; - this._nodes[i] = newnode; - newnode.configure(node.serialize()); - newnode.graph = this; - this._nodes_by_id[newnode.id] = newnode; - if (node.inputs) { - newnode.inputs = node.inputs.concat(); - } - if (node.outputs) { - newnode.outputs = node.outputs.concat(); - } - } - this.updateExecutionOrder(); - }; - - // ********** GLOBALS ***************** - - LGraph.prototype.onAction = function(action, param) { - this._input_nodes = this.findNodesByClass( - LiteGraph.GraphInput, - this._input_nodes - ); - for (var i = 0; i < this._input_nodes.length; ++i) { - var node = this._input_nodes[i]; - if (node.properties.name != action) { - continue; - } - node.onAction(action, param); - break; - } - }; - - LGraph.prototype.trigger = function(action, param) { - if (this.onTrigger) { - this.onTrigger(action, param); - } - }; - - /** - * Tell this graph it has a global graph input of this type - * @method addGlobalInput - * @param {String} name - * @param {String} type - * @param {*} value [optional] - */ - LGraph.prototype.addInput = function(name, type, value) { - var input = this.inputs[name]; - if (input) { - //already exist - return; - } - - this.inputs[name] = { name: name, type: type, value: value }; - this._version++; - - if (this.onInputAdded) { - this.onInputAdded(name, type); - } - - if (this.onInputsOutputsChange) { - this.onInputsOutputsChange(); - } - }; - - /** - * Assign a data to the global graph input - * @method setGlobalInputData - * @param {String} name - * @param {*} data - */ - LGraph.prototype.setInputData = function(name, data) { - var input = this.inputs[name]; - if (!input) { - return; - } - input.value = data; - }; - - /** - * Returns the current value of a global graph input - * @method getInputData - * @param {String} name - * @return {*} the data - */ - LGraph.prototype.getInputData = function(name) { - var input = this.inputs[name]; - if (!input) { - return null; - } - return input.value; - }; - - /** - * Changes the name of a global graph input - * @method renameInput - * @param {String} old_name - * @param {String} new_name - */ - LGraph.prototype.renameInput = function(old_name, name) { - if (name == old_name) { - return; - } - - if (!this.inputs[old_name]) { - return false; - } - - if (this.inputs[name]) { - console.error("there is already one input with that name"); - return false; - } - - this.inputs[name] = this.inputs[old_name]; - delete this.inputs[old_name]; - this._version++; - - if (this.onInputRenamed) { - this.onInputRenamed(old_name, name); - } - - if (this.onInputsOutputsChange) { - this.onInputsOutputsChange(); - } - }; - - /** - * Changes the type of a global graph input - * @method changeInputType - * @param {String} name - * @param {String} type - */ - LGraph.prototype.changeInputType = function(name, type) { - if (!this.inputs[name]) { - return false; - } - - if ( - this.inputs[name].type && - String(this.inputs[name].type).toLowerCase() == - String(type).toLowerCase() - ) { - return; - } - - this.inputs[name].type = type; - this._version++; - if (this.onInputTypeChanged) { - this.onInputTypeChanged(name, type); - } - }; - - /** - * Removes a global graph input - * @method removeInput - * @param {String} name - * @param {String} type - */ - LGraph.prototype.removeInput = function(name) { - if (!this.inputs[name]) { - return false; - } - - delete this.inputs[name]; - this._version++; - - if (this.onInputRemoved) { - this.onInputRemoved(name); - } - - if (this.onInputsOutputsChange) { - this.onInputsOutputsChange(); - } - return true; - }; - - /** - * Creates a global graph output - * @method addOutput - * @param {String} name - * @param {String} type - * @param {*} value - */ - LGraph.prototype.addOutput = function(name, type, value) { - this.outputs[name] = { name: name, type: type, value: value }; - this._version++; - - if (this.onOutputAdded) { - this.onOutputAdded(name, type); - } - - if (this.onInputsOutputsChange) { - this.onInputsOutputsChange(); - } - }; - - /** - * Assign a data to the global output - * @method setOutputData - * @param {String} name - * @param {String} value - */ - LGraph.prototype.setOutputData = function(name, value) { - var output = this.outputs[name]; - if (!output) { - return; - } - output.value = value; - }; - - /** - * Returns the current value of a global graph output - * @method getOutputData - * @param {String} name - * @return {*} the data - */ - LGraph.prototype.getOutputData = function(name) { - var output = this.outputs[name]; - if (!output) { - return null; - } - return output.value; - }; - - /** - * Renames a global graph output - * @method renameOutput - * @param {String} old_name - * @param {String} new_name - */ - LGraph.prototype.renameOutput = function(old_name, name) { - if (!this.outputs[old_name]) { - return false; - } - - if (this.outputs[name]) { - console.error("there is already one output with that name"); - return false; - } - - this.outputs[name] = this.outputs[old_name]; - delete this.outputs[old_name]; - this._version++; - - if (this.onOutputRenamed) { - this.onOutputRenamed(old_name, name); - } - - if (this.onInputsOutputsChange) { - this.onInputsOutputsChange(); - } - }; - - /** - * Changes the type of a global graph output - * @method changeOutputType - * @param {String} name - * @param {String} type - */ - LGraph.prototype.changeOutputType = function(name, type) { - if (!this.outputs[name]) { - return false; - } - - if ( - this.outputs[name].type && - String(this.outputs[name].type).toLowerCase() == - String(type).toLowerCase() - ) { - return; - } - - this.outputs[name].type = type; - this._version++; - if (this.onOutputTypeChanged) { - this.onOutputTypeChanged(name, type); - } - }; - - /** - * Removes a global graph output - * @method removeOutput - * @param {String} name - */ - LGraph.prototype.removeOutput = function(name) { - if (!this.outputs[name]) { - return false; - } - delete this.outputs[name]; - this._version++; - - if (this.onOutputRemoved) { - this.onOutputRemoved(name); - } - - if (this.onInputsOutputsChange) { - this.onInputsOutputsChange(); - } - return true; - }; - - LGraph.prototype.triggerInput = function(name, value) { - var nodes = this.findNodesByTitle(name); - for (var i = 0; i < nodes.length; ++i) { - nodes[i].onTrigger(value); - } - }; - - LGraph.prototype.setCallback = function(name, func) { - var nodes = this.findNodesByTitle(name); - for (var i = 0; i < nodes.length; ++i) { - nodes[i].setTrigger(func); - } - }; - - LGraph.prototype.connectionChange = function(node, link_info) { - this.updateExecutionOrder(); - if (this.onConnectionChange) { - this.onConnectionChange(node); - } - this._version++; - this.sendActionToCanvas("onConnectionChange"); - }; - - /** - * returns if the graph is in live mode - * @method isLive - */ - - LGraph.prototype.isLive = function() { - if (!this.list_of_graphcanvas) { - return false; - } - - for (var i = 0; i < this.list_of_graphcanvas.length; ++i) { - var c = this.list_of_graphcanvas[i]; - if (c.live_mode) { - return true; - } - } - return false; - }; - - /** - * clears the triggered slot animation in all links (stop visual animation) - * @method clearTriggeredSlots - */ - LGraph.prototype.clearTriggeredSlots = function() { - for (var i in this.links) { - var link_info = this.links[i]; - if (!link_info) { - continue; - } - if (link_info._last_time) { - link_info._last_time = 0; - } - } - }; - - /* Called when something visually changed (not the graph!) */ - LGraph.prototype.change = function() { - if (LiteGraph.debug) { - console.log("Graph changed"); - } - this.sendActionToCanvas("setDirty", [true, true]); - if (this.on_change) { - this.on_change(this); - } - }; - - LGraph.prototype.setDirtyCanvas = function(fg, bg) { - this.sendActionToCanvas("setDirty", [fg, bg]); - }; - - /** - * Destroys a link - * @method removeLink - * @param {Number} link_id - */ - LGraph.prototype.removeLink = function(link_id) { - var link = this.links[link_id]; - if (!link) { - return; - } - var node = this.getNodeById(link.target_id); - if (node) { - node.disconnectInput(link.target_slot); - } - }; - - //save and recover app state *************************************** - /** - * Creates a Object containing all the info about this graph, it can be serialized - * @method serialize - * @return {Object} value of the node - */ - LGraph.prototype.serialize = function() { - var nodes_info = []; - for (var i = 0, l = this._nodes.length; i < l; ++i) { - nodes_info.push(this._nodes[i].serialize()); - } - - //pack link info into a non-verbose format - var links = []; - for (var i in this.links) { - //links is an OBJECT - var link = this.links[i]; - if (!link.serialize) { - //weird bug I havent solved yet - console.warn( - "weird LLink bug, link info is not a LLink but a regular object" - ); - var link2 = new LLink(); - for (var j in link) { - link2[j] = link[j]; - } - this.links[i] = link2; - link = link2; - } - - links.push(link.serialize()); - } - - var groups_info = []; - for (var i = 0; i < this._groups.length; ++i) { - groups_info.push(this._groups[i].serialize()); - } - - var data = { - last_node_id: this.last_node_id, - last_link_id: this.last_link_id, - nodes: nodes_info, - links: links, - groups: groups_info, - config: this.config, - version: LiteGraph.VERSION - }; - - return data; - }; - - /** - * Configure a graph from a JSON string - * @method configure - * @param {String} str configure a graph from a JSON string - * @param {Boolean} returns if there was any error parsing - */ - LGraph.prototype.configure = function(data, keep_old) { - if (!data) { - return; - } - - if (!keep_old) { - this.clear(); - } - - var nodes = data.nodes; - - //decode links info (they are very verbose) - if (data.links && data.links.constructor === Array) { - var links = []; - for (var i = 0; i < data.links.length; ++i) { - var link_data = data.links[i]; - if(!link_data) //weird bug - { - console.warn("serialized graph link data contains errors, skipping."); - continue; - } - var link = new LLink(); - link.configure(link_data); - links[link.id] = link; - } - data.links = links; - } - - //copy all stored fields - for (var i in data) { - if(i == "nodes" || i == "groups") - continue; - this[i] = data[i]; - } - - var error = false; - - //create nodes - this._nodes = []; - if (nodes) { - for (var i = 0, l = nodes.length; i < l; ++i) { - var n_info = nodes[i]; //stored info - var node = LiteGraph.createNode(n_info.type, n_info.title); - if (!node) { - if (LiteGraph.debug) { - console.log( - "Node not found or has errors: " + n_info.type - ); - } - - //in case of error we create a replacement node to avoid losing info - node = new LGraphNode(); - node.last_serialization = n_info; - node.has_errors = true; - error = true; - //continue; - } - - node.id = n_info.id; //id it or it will create a new id - this.add(node, true); //add before configure, otherwise configure cannot create links - } - - //configure nodes afterwards so they can reach each other - for (var i = 0, l = nodes.length; i < l; ++i) { - var n_info = nodes[i]; - var node = this.getNodeById(n_info.id); - if (node) { - node.configure(n_info); - } - } - } - - //groups - this._groups.length = 0; - if (data.groups) { - for (var i = 0; i < data.groups.length; ++i) { - var group = new LiteGraph.LGraphGroup(); - group.configure(data.groups[i]); - this.add(group); - } - } - - this.updateExecutionOrder(); - this._version++; - this.setDirtyCanvas(true, true); - return error; - }; - - LGraph.prototype.load = function(url) { - var that = this; - var req = new XMLHttpRequest(); - req.open("GET", url, true); - req.send(null); - req.onload = function(oEvent) { - if (req.status !== 200) { - console.error("Error loading graph:", req.status, req.response); - return; - } - var data = JSON.parse(req.response); - that.configure(data); - }; - req.onerror = function(err) { - console.error("Error loading graph:", err); - }; - }; - - LGraph.prototype.onNodeTrace = function(node, msg, color) { - //TODO - }; - - //this is the class in charge of storing link information - function LLink(id, type, origin_id, origin_slot, target_id, target_slot) { - this.id = id; - this.type = type; - this.origin_id = origin_id; - this.origin_slot = origin_slot; - this.target_id = target_id; - this.target_slot = target_slot; - - this._data = null; - this._pos = new Float32Array(2); //center - } - - LLink.prototype.configure = function(o) { - if (o.constructor === Array) { - this.id = o[0]; - this.origin_id = o[1]; - this.origin_slot = o[2]; - this.target_id = o[3]; - this.target_slot = o[4]; - this.type = o[5]; - } else { - this.id = o.id; - this.type = o.type; - this.origin_id = o.origin_id; - this.origin_slot = o.origin_slot; - this.target_id = o.target_id; - this.target_slot = o.target_slot; - } - }; - - LLink.prototype.serialize = function() { - return [ - this.id, - this.origin_id, - this.origin_slot, - this.target_id, - this.target_slot, - this.type - ]; - }; - - LiteGraph.LLink = LLink; - - // ************************************************************* - // Node CLASS ******* - // ************************************************************* - - /* - title: string - pos: [x,y] - size: [x,y] - - input|output: every connection - + { name:string, type:string, pos: [x,y]=Optional, direction: "input"|"output", links: Array }); - - general properties: - + clip_area: if you render outside the node, it will be clipped - + unsafe_execution: not allowed for safe execution - + skip_repeated_outputs: when adding new outputs, it wont show if there is one already connected - + resizable: if set to false it wont be resizable with the mouse - + horizontal: slots are distributed horizontally - + widgets_start_y: widgets start at y distance from the top of the node - - flags object: - + collapsed: if it is collapsed - - supported callbacks: - + onAdded: when added to graph (warning: this is called BEFORE the node is configured when loading) - + onRemoved: when removed from graph - + onStart: when the graph starts playing - + onStop: when the graph stops playing - + onDrawForeground: render the inside widgets inside the node - + onDrawBackground: render the background area inside the node (only in edit mode) - + onMouseDown - + onMouseMove - + onMouseUp - + onMouseEnter - + onMouseLeave - + onExecute: execute the node - + onPropertyChanged: when a property is changed in the panel (return true to skip default behaviour) - + onGetInputs: returns an array of possible inputs - + onGetOutputs: returns an array of possible outputs - + onBounding: in case this node has a bigger bounding than the node itself (the callback receives the bounding as [x,y,w,h]) - + onDblClick: double clicked in the node - + onInputDblClick: input slot double clicked (can be used to automatically create a node connected) - + onOutputDblClick: output slot double clicked (can be used to automatically create a node connected) - + onConfigure: called after the node has been configured - + onSerialize: to add extra info when serializing (the callback receives the object that should be filled with the data) - + onSelected - + onDeselected - + onDropItem : DOM item dropped over the node - + onDropFile : file dropped over the node - + onConnectInput : if returns false the incoming connection will be canceled - + onConnectionsChange : a connection changed (new one or removed) (LiteGraph.INPUT or LiteGraph.OUTPUT, slot, true if connected, link_info, input_info ) - + onAction: action slot triggered - + getExtraMenuOptions: to add option to context menu -*/ - - /** - * Base Class for all the node type classes - * @class LGraphNode - * @param {String} name a name for the node - */ - - function LGraphNode(title) { - this._ctor(title); - } - - global.LGraphNode = LiteGraph.LGraphNode = LGraphNode; - - LGraphNode.prototype._ctor = function(title) { - this.title = title || "Unnamed"; - this.size = [LiteGraph.NODE_WIDTH, 60]; - this.graph = null; - - this._pos = new Float32Array(10, 10); - - Object.defineProperty(this, "pos", { - set: function(v) { - if (!v || v.length < 2) { - return; - } - this._pos[0] = v[0]; - this._pos[1] = v[1]; - }, - get: function() { - return this._pos; - }, - enumerable: true - }); - - this.id = -1; //not know till not added - this.type = null; - - //inputs available: array of inputs - this.inputs = []; - this.outputs = []; - this.connections = []; - - //local data - this.properties = {}; //for the values - this.properties_info = []; //for the info - - this.flags = {}; - }; - - /** - * configure a node from an object containing the serialized info - * @method configure - */ - LGraphNode.prototype.configure = function(info) { - if (this.graph) { - this.graph._version++; - } - for (var j in info) { - if (j == "properties") { - //i don't want to clone properties, I want to reuse the old container - for (var k in info.properties) { - this.properties[k] = info.properties[k]; - if (this.onPropertyChanged) { - this.onPropertyChanged( k, info.properties[k] ); - } - } - continue; - } - - if (info[j] == null) { - continue; - } else if (typeof info[j] == "object") { - //object - if (this[j] && this[j].configure) { - this[j].configure(info[j]); - } else { - this[j] = LiteGraph.cloneObject(info[j], this[j]); - } - } //value - else { - this[j] = info[j]; - } - } - - if (!info.title) { - this.title = this.constructor.title; - } - - if (this.onConnectionsChange) { - if (this.inputs) { - for (var i = 0; i < this.inputs.length; ++i) { - var input = this.inputs[i]; - var link_info = this.graph - ? this.graph.links[input.link] - : null; - this.onConnectionsChange( - LiteGraph.INPUT, - i, - true, - link_info, - input - ); //link_info has been created now, so its updated - } - } - - if (this.outputs) { - for (var i = 0; i < this.outputs.length; ++i) { - var output = this.outputs[i]; - if (!output.links) { - continue; - } - for (var j = 0; j < output.links.length; ++j) { - var link_info = this.graph - ? this.graph.links[output.links[j]] - : null; - this.onConnectionsChange( - LiteGraph.OUTPUT, - i, - true, - link_info, - output - ); //link_info has been created now, so its updated - } - } - } - } - - if( this.widgets ) - { - for (var i = 0; i < this.widgets.length; ++i) - { - var w = this.widgets[i]; - if(w.options && w.options.property && this.properties[ w.options.property ]) - w.value = JSON.parse( JSON.stringify( this.properties[ w.options.property ] ) ); - } - if (info.widgets_values) { - for (var i = 0; i < info.widgets_values.length; ++i) { - if (this.widgets[i]) { - this.widgets[i].value = info.widgets_values[i]; - } - } - } - } - - if (this.onConfigure) { - this.onConfigure(info); - } - }; - - /** - * serialize the content - * @method serialize - */ - - LGraphNode.prototype.serialize = function() { - //create serialization object - var o = { - id: this.id, - type: this.type, - pos: this.pos, - size: this.size, - flags: LiteGraph.cloneObject(this.flags), - order: this.order, - mode: this.mode - }; - - //special case for when there were errors - if (this.constructor === LGraphNode && this.last_serialization) { - return this.last_serialization; - } - - if (this.inputs) { - o.inputs = this.inputs; - } - - if (this.outputs) { - //clear outputs last data (because data in connections is never serialized but stored inside the outputs info) - for (var i = 0; i < this.outputs.length; i++) { - delete this.outputs[i]._data; - } - o.outputs = this.outputs; - } - - if (this.title && this.title != this.constructor.title) { - o.title = this.title; - } - - if (this.properties) { - o.properties = LiteGraph.cloneObject(this.properties); - } - - if (this.widgets && this.serialize_widgets) { - o.widgets_values = []; - for (var i = 0; i < this.widgets.length; ++i) { - o.widgets_values[i] = this.widgets[i].value; - } - } - - if (!o.type) { - o.type = this.constructor.type; - } - - if (this.color) { - o.color = this.color; - } - if (this.bgcolor) { - o.bgcolor = this.bgcolor; - } - if (this.boxcolor) { - o.boxcolor = this.boxcolor; - } - if (this.shape) { - o.shape = this.shape; - } - - if (this.onSerialize) { - if (this.onSerialize(o)) { - console.warn( - "node onSerialize shouldnt return anything, data should be stored in the object pass in the first parameter" - ); - } - } - - return o; - }; - - /* Creates a clone of this node */ - LGraphNode.prototype.clone = function() { - var node = LiteGraph.createNode(this.type); - if (!node) { - return null; - } - - //we clone it because serialize returns shared containers - var data = LiteGraph.cloneObject(this.serialize()); - - //remove links - if (data.inputs) { - for (var i = 0; i < data.inputs.length; ++i) { - data.inputs[i].link = null; - } - } - - if (data.outputs) { - for (var i = 0; i < data.outputs.length; ++i) { - if (data.outputs[i].links) { - data.outputs[i].links.length = 0; - } - } - } - - delete data["id"]; - //remove links - node.configure(data); - - return node; - }; - - /** - * serialize and stringify - * @method toString - */ - - LGraphNode.prototype.toString = function() { - return JSON.stringify(this.serialize()); - }; - //LGraphNode.prototype.deserialize = function(info) {} //this cannot be done from within, must be done in LiteGraph - - /** - * get the title string - * @method getTitle - */ - - LGraphNode.prototype.getTitle = function() { - return this.title || this.constructor.title; - }; - - /** - * sets the value of a property - * @method setProperty - * @param {String} name - * @param {*} value - */ - LGraphNode.prototype.setProperty = function(name, value) { - if (!this.properties) { - this.properties = {}; - } - if( value === this.properties[name] ) - return; - var prev_value = this.properties[name]; - this.properties[name] = value; - if (this.onPropertyChanged) { - if( this.onPropertyChanged(name, value, prev_value) === false ) //abort change - this.properties[name] = prev_value; - } - }; - - // Execution ************************* - /** - * sets the output data - * @method setOutputData - * @param {number} slot - * @param {*} data - */ - LGraphNode.prototype.setOutputData = function(slot, data) { - if (!this.outputs) { - return; - } - - //this maybe slow and a niche case - //if(slot && slot.constructor === String) - // slot = this.findOutputSlot(slot); - - if (slot == -1 || slot >= this.outputs.length) { - return; - } - - var output_info = this.outputs[slot]; - if (!output_info) { - return; - } - - //store data in the output itself in case we want to debug - output_info._data = data; - - //if there are connections, pass the data to the connections - if (this.outputs[slot].links) { - for (var i = 0; i < this.outputs[slot].links.length; i++) { - var link_id = this.outputs[slot].links[i]; - var link = this.graph.links[link_id]; - if(link) - link.data = data; - } - } - }; - - /** - * sets the output data type, useful when you want to be able to overwrite the data type - * @method setOutputDataType - * @param {number} slot - * @param {String} datatype - */ - LGraphNode.prototype.setOutputDataType = function(slot, type) { - if (!this.outputs) { - return; - } - if (slot == -1 || slot >= this.outputs.length) { - return; - } - var output_info = this.outputs[slot]; - if (!output_info) { - return; - } - //store data in the output itself in case we want to debug - output_info.type = type; - - //if there are connections, pass the data to the connections - if (this.outputs[slot].links) { - for (var i = 0; i < this.outputs[slot].links.length; i++) { - var link_id = this.outputs[slot].links[i]; - this.graph.links[link_id].type = type; - } - } - }; - - /** - * Retrieves the input data (data traveling through the connection) from one slot - * @method getInputData - * @param {number} slot - * @param {boolean} force_update if set to true it will force the connected node of this slot to output data into this link - * @return {*} data or if it is not connected returns undefined - */ - LGraphNode.prototype.getInputData = function(slot, force_update) { - if (!this.inputs) { - return; - } //undefined; - - if (slot >= this.inputs.length || this.inputs[slot].link == null) { - return; - } - - var link_id = this.inputs[slot].link; - var link = this.graph.links[link_id]; - if (!link) { - //bug: weird case but it happens sometimes - return null; - } - - if (!force_update) { - return link.data; - } - - //special case: used to extract data from the incoming connection before the graph has been executed - var node = this.graph.getNodeById(link.origin_id); - if (!node) { - return link.data; - } - - if (node.updateOutputData) { - node.updateOutputData(link.origin_slot); - } else if (node.onExecute) { - node.onExecute(); - } - - return link.data; - }; - - /** - * Retrieves the input data type (in case this supports multiple input types) - * @method getInputDataType - * @param {number} slot - * @return {String} datatype in string format - */ - LGraphNode.prototype.getInputDataType = function(slot) { - if (!this.inputs) { - return null; - } //undefined; - - if (slot >= this.inputs.length || this.inputs[slot].link == null) { - return null; - } - var link_id = this.inputs[slot].link; - var link = this.graph.links[link_id]; - if (!link) { - //bug: weird case but it happens sometimes - return null; - } - var node = this.graph.getNodeById(link.origin_id); - if (!node) { - return link.type; - } - var output_info = node.outputs[link.origin_slot]; - if (output_info) { - return output_info.type; - } - return null; - }; - - /** - * Retrieves the input data from one slot using its name instead of slot number - * @method getInputDataByName - * @param {String} slot_name - * @param {boolean} force_update if set to true it will force the connected node of this slot to output data into this link - * @return {*} data or if it is not connected returns null - */ - LGraphNode.prototype.getInputDataByName = function( - slot_name, - force_update - ) { - var slot = this.findInputSlot(slot_name); - if (slot == -1) { - return null; - } - return this.getInputData(slot, force_update); - }; - - /** - * tells you if there is a connection in one input slot - * @method isInputConnected - * @param {number} slot - * @return {boolean} - */ - LGraphNode.prototype.isInputConnected = function(slot) { - if (!this.inputs) { - return false; - } - return slot < this.inputs.length && this.inputs[slot].link != null; - }; - - /** - * tells you info about an input connection (which node, type, etc) - * @method getInputInfo - * @param {number} slot - * @return {Object} object or null { link: id, name: string, type: string or 0 } - */ - LGraphNode.prototype.getInputInfo = function(slot) { - if (!this.inputs) { - return null; - } - if (slot < this.inputs.length) { - return this.inputs[slot]; - } - return null; - }; - - /** - * returns the node connected in the input slot - * @method getInputNode - * @param {number} slot - * @return {LGraphNode} node or null - */ - LGraphNode.prototype.getInputNode = function(slot) { - if (!this.inputs) { - return null; - } - if (slot >= this.inputs.length) { - return null; - } - var input = this.inputs[slot]; - if (!input || input.link === null) { - return null; - } - var link_info = this.graph.links[input.link]; - if (!link_info) { - return null; - } - return this.graph.getNodeById(link_info.origin_id); - }; - - /** - * returns the value of an input with this name, otherwise checks if there is a property with that name - * @method getInputOrProperty - * @param {string} name - * @return {*} value - */ - LGraphNode.prototype.getInputOrProperty = function(name) { - if (!this.inputs || !this.inputs.length) { - return this.properties ? this.properties[name] : null; - } - - for (var i = 0, l = this.inputs.length; i < l; ++i) { - var input_info = this.inputs[i]; - if (name == input_info.name && input_info.link != null) { - var link = this.graph.links[input_info.link]; - if (link) { - return link.data; - } - } - } - return this.properties[name]; - }; - - /** - * tells you the last output data that went in that slot - * @method getOutputData - * @param {number} slot - * @return {Object} object or null - */ - LGraphNode.prototype.getOutputData = function(slot) { - if (!this.outputs) { - return null; - } - if (slot >= this.outputs.length) { - return null; - } - - var info = this.outputs[slot]; - return info._data; - }; - - /** - * tells you info about an output connection (which node, type, etc) - * @method getOutputInfo - * @param {number} slot - * @return {Object} object or null { name: string, type: string, links: [ ids of links in number ] } - */ - LGraphNode.prototype.getOutputInfo = function(slot) { - if (!this.outputs) { - return null; - } - if (slot < this.outputs.length) { - return this.outputs[slot]; - } - return null; - }; - - /** - * tells you if there is a connection in one output slot - * @method isOutputConnected - * @param {number} slot - * @return {boolean} - */ - LGraphNode.prototype.isOutputConnected = function(slot) { - if (!this.outputs) { - return false; - } - return ( - slot < this.outputs.length && - this.outputs[slot].links && - this.outputs[slot].links.length - ); - }; - - /** - * tells you if there is any connection in the output slots - * @method isAnyOutputConnected - * @return {boolean} - */ - LGraphNode.prototype.isAnyOutputConnected = function() { - if (!this.outputs) { - return false; - } - for (var i = 0; i < this.outputs.length; ++i) { - if (this.outputs[i].links && this.outputs[i].links.length) { - return true; - } - } - return false; - }; - - /** - * retrieves all the nodes connected to this output slot - * @method getOutputNodes - * @param {number} slot - * @return {array} - */ - LGraphNode.prototype.getOutputNodes = function(slot) { - if (!this.outputs || this.outputs.length == 0) { - return null; - } - - if (slot >= this.outputs.length) { - return null; - } - - var output = this.outputs[slot]; - if (!output.links || output.links.length == 0) { - return null; - } - - var r = []; - for (var i = 0; i < output.links.length; i++) { - var link_id = output.links[i]; - var link = this.graph.links[link_id]; - if (link) { - var target_node = this.graph.getNodeById(link.target_id); - if (target_node) { - r.push(target_node); - } - } - } - return r; - }; - - /** - * Triggers an event in this node, this will trigger any output with the same name - * @method trigger - * @param {String} event name ( "on_play", ... ) if action is equivalent to false then the event is send to all - * @param {*} param - */ - LGraphNode.prototype.trigger = function(action, param) { - if (!this.outputs || !this.outputs.length) { - return; - } - - if (this.graph) - this.graph._last_trigger_time = LiteGraph.getTime(); - - for (var i = 0; i < this.outputs.length; ++i) { - var output = this.outputs[i]; - if ( !output || output.type !== LiteGraph.EVENT || (action && output.name != action) ) - continue; - this.triggerSlot(i, param); - } - }; - - /** - * Triggers an slot event in this node - * @method triggerSlot - * @param {Number} slot the index of the output slot - * @param {*} param - * @param {Number} link_id [optional] in case you want to trigger and specific output link in a slot - */ - LGraphNode.prototype.triggerSlot = function(slot, param, link_id) { - if (!this.outputs) { - return; - } - - var output = this.outputs[slot]; - if (!output) { - return; - } - - var links = output.links; - if (!links || !links.length) { - return; - } - - if (this.graph) { - this.graph._last_trigger_time = LiteGraph.getTime(); - } - - //for every link attached here - for (var k = 0; k < links.length; ++k) { - var id = links[k]; - if (link_id != null && link_id != id) { - //to skip links - continue; - } - var link_info = this.graph.links[links[k]]; - if (!link_info) { - //not connected - continue; - } - link_info._last_time = LiteGraph.getTime(); - var node = this.graph.getNodeById(link_info.target_id); - if (!node) { - //node not found? - continue; - } - - //used to mark events in graph - var target_connection = node.inputs[link_info.target_slot]; - - if (node.onAction) { - node.onAction(target_connection.name, param); - } else if (node.mode === LiteGraph.ON_TRIGGER) { - if (node.onExecute) { - node.onExecute(param); - } - } - } - }; - - /** - * clears the trigger slot animation - * @method clearTriggeredSlot - * @param {Number} slot the index of the output slot - * @param {Number} link_id [optional] in case you want to trigger and specific output link in a slot - */ - LGraphNode.prototype.clearTriggeredSlot = function(slot, link_id) { - if (!this.outputs) { - return; - } - - var output = this.outputs[slot]; - if (!output) { - return; - } - - var links = output.links; - if (!links || !links.length) { - return; - } - - //for every link attached here - for (var k = 0; k < links.length; ++k) { - var id = links[k]; - if (link_id != null && link_id != id) { - //to skip links - continue; - } - var link_info = this.graph.links[links[k]]; - if (!link_info) { - //not connected - continue; - } - link_info._last_time = 0; - } - }; - - /** - * add a new property to this node - * @method addProperty - * @param {string} name - * @param {*} default_value - * @param {string} type string defining the output type ("vec3","number",...) - * @param {Object} extra_info this can be used to have special properties of the property (like values, etc) - */ - LGraphNode.prototype.addProperty = function( - name, - default_value, - type, - extra_info - ) { - var o = { name: name, type: type, default_value: default_value }; - if (extra_info) { - for (var i in extra_info) { - o[i] = extra_info[i]; - } - } - if (!this.properties_info) { - this.properties_info = []; - } - this.properties_info.push(o); - if (!this.properties) { - this.properties = {}; - } - this.properties[name] = default_value; - return o; - }; - - //connections - - /** - * add a new output slot to use in this node - * @method addOutput - * @param {string} name - * @param {string} type string defining the output type ("vec3","number",...) - * @param {Object} extra_info this can be used to have special properties of an output (label, special color, position, etc) - */ - LGraphNode.prototype.addOutput = function(name, type, extra_info) { - var o = { name: name, type: type, links: null }; - if (extra_info) { - for (var i in extra_info) { - o[i] = extra_info[i]; - } - } - - if (!this.outputs) { - this.outputs = []; - } - this.outputs.push(o); - if (this.onOutputAdded) { - this.onOutputAdded(o); - } - this.size = this.computeSize(); - this.setDirtyCanvas(true, true); - return o; - }; - - /** - * add a new output slot to use in this node - * @method addOutputs - * @param {Array} array of triplets like [[name,type,extra_info],[...]] - */ - LGraphNode.prototype.addOutputs = function(array) { - for (var i = 0; i < array.length; ++i) { - var info = array[i]; - var o = { name: info[0], type: info[1], link: null }; - if (array[2]) { - for (var j in info[2]) { - o[j] = info[2][j]; - } - } - - if (!this.outputs) { - this.outputs = []; - } - this.outputs.push(o); - if (this.onOutputAdded) { - this.onOutputAdded(o); - } - } - - this.size = this.computeSize(); - this.setDirtyCanvas(true, true); - }; - - /** - * remove an existing output slot - * @method removeOutput - * @param {number} slot - */ - LGraphNode.prototype.removeOutput = function(slot) { - this.disconnectOutput(slot); - this.outputs.splice(slot, 1); - for (var i = slot; i < this.outputs.length; ++i) { - if (!this.outputs[i] || !this.outputs[i].links) { - continue; - } - var links = this.outputs[i].links; - for (var j = 0; j < links.length; ++j) { - var link = this.graph.links[links[j]]; - if (!link) { - continue; - } - link.origin_slot -= 1; - } - } - - this.size = this.computeSize(); - if (this.onOutputRemoved) { - this.onOutputRemoved(slot); - } - this.setDirtyCanvas(true, true); - }; - - /** - * add a new input slot to use in this node - * @method addInput - * @param {string} name - * @param {string} type string defining the input type ("vec3","number",...), it its a generic one use 0 - * @param {Object} extra_info this can be used to have special properties of an input (label, color, position, etc) - */ - LGraphNode.prototype.addInput = function(name, type, extra_info) { - type = type || 0; - var o = { name: name, type: type, link: null }; - if (extra_info) { - for (var i in extra_info) { - o[i] = extra_info[i]; - } - } - - if (!this.inputs) { - this.inputs = []; - } - this.inputs.push(o); - this.size = this.computeSize(); - if (this.onInputAdded) { - this.onInputAdded(o); - } - this.setDirtyCanvas(true, true); - return o; - }; - - /** - * add several new input slots in this node - * @method addInputs - * @param {Array} array of triplets like [[name,type,extra_info],[...]] - */ - LGraphNode.prototype.addInputs = function(array) { - for (var i = 0; i < array.length; ++i) { - var info = array[i]; - var o = { name: info[0], type: info[1], link: null }; - if (array[2]) { - for (var j in info[2]) { - o[j] = info[2][j]; - } - } - - if (!this.inputs) { - this.inputs = []; - } - this.inputs.push(o); - if (this.onInputAdded) { - this.onInputAdded(o); - } - } - - this.size = this.computeSize(); - this.setDirtyCanvas(true, true); - }; - - /** - * remove an existing input slot - * @method removeInput - * @param {number} slot - */ - LGraphNode.prototype.removeInput = function(slot) { - this.disconnectInput(slot); - this.inputs.splice(slot, 1); - for (var i = slot; i < this.inputs.length; ++i) { - if (!this.inputs[i]) { - continue; - } - var link = this.graph.links[this.inputs[i].link]; - if (!link) { - continue; - } - link.target_slot -= 1; - } - this.size = this.computeSize(); - if (this.onInputRemoved) { - this.onInputRemoved(slot); - } - this.setDirtyCanvas(true, true); - }; - - /** - * add an special connection to this node (used for special kinds of graphs) - * @method addConnection - * @param {string} name - * @param {string} type string defining the input type ("vec3","number",...) - * @param {[x,y]} pos position of the connection inside the node - * @param {string} direction if is input or output - */ - LGraphNode.prototype.addConnection = function(name, type, pos, direction) { - var o = { - name: name, - type: type, - pos: pos, - direction: direction, - links: null - }; - this.connections.push(o); - return o; - }; - - /** - * computes the size of a node according to its inputs and output slots - * @method computeSize - * @param {number} minHeight - * @return {number} the total size - */ - LGraphNode.prototype.computeSize = function(minHeight, out) { - if (this.constructor.size) { - return this.constructor.size.concat(); - } - - var rows = Math.max( - this.inputs ? this.inputs.length : 1, - this.outputs ? this.outputs.length : 1 - ); - var size = out || new Float32Array([0, 0]); - rows = Math.max(rows, 1); - var font_size = LiteGraph.NODE_TEXT_SIZE; //although it should be graphcanvas.inner_text_font size - size[1] = (this.constructor.slot_start_y || 0) + rows * LiteGraph.NODE_SLOT_HEIGHT; - - var widgets_height = 0; - if (this.widgets && this.widgets.length) { - widgets_height = this.widgets.length * (LiteGraph.NODE_WIDGET_HEIGHT + 4) + 8; - } - - //compute height using widgets height - if( this.widgets_up ) - size[1] = Math.max( size[1], widgets_height ); - else if( this.widgets_start_y != null ) - size[1] = Math.max( size[1], widgets_height + this.widgets_start_y ); - else - size[1] += widgets_height; - - var font_size = font_size; - var title_width = compute_text_size(this.title); - var input_width = 0; - var output_width = 0; - - if (this.inputs) { - for (var i = 0, l = this.inputs.length; i < l; ++i) { - var input = this.inputs[i]; - var text = input.label || input.name || ""; - var text_width = compute_text_size(text); - if (input_width < text_width) { - input_width = text_width; - } - } - } - - if (this.outputs) { - for (var i = 0, l = this.outputs.length; i < l; ++i) { - var output = this.outputs[i]; - var text = output.label || output.name || ""; - var text_width = compute_text_size(text); - if (output_width < text_width) { - output_width = text_width; - } - } - } - - size[0] = Math.max(input_width + output_width + 10, title_width); - size[0] = Math.max(size[0], LiteGraph.NODE_WIDTH); - if (this.widgets && this.widgets.length) { - size[0] = Math.max(size[0], LiteGraph.NODE_WIDTH * 1.5); - } - - if (this.onResize) { - this.onResize(size); - } - - function compute_text_size(text) { - if (!text) { - return 0; - } - return font_size * text.length * 0.6; - } - - if ( - this.constructor.min_height && - size[1] < this.constructor.min_height - ) { - size[1] = this.constructor.min_height; - } - - size[1] += 6; //margin - - return size; - }; - - /** - * Allows to pass - * - * @method addWidget - * @param {String} type the widget type (could be "number","string","combo" - * @param {String} name the text to show on the widget - * @param {String} value the default value - * @param {Function} callback function to call when it changes (optionally, it can be the name of the property to modify) - * @param {Object} options the object that contains special properties of this widget - * @return {Object} the created widget object - */ - LGraphNode.prototype.addWidget = function( type, name, value, callback, options ) - { - if (!this.widgets) { - this.widgets = []; - } - - if(!options && callback && callback.constructor === Object) - { - options = callback; - callback = null; - } - - if(options && options.constructor === String) //options can be the property name - options = { property: options }; - - if(callback && callback.constructor === String) //callback can be the property name - { - if(!options) - options = {}; - options.property = callback; - callback = null; - } - - if(callback && callback.constructor !== Function) - { - console.warn("addWidget: callback must be a function"); - callback = null; - } - - var w = { - type: type.toLowerCase(), - name: name, - value: value, - callback: callback, - options: options || {} - }; - - if (w.options.y !== undefined) { - w.y = w.options.y; - } - - if (!callback && !w.options.callback && !w.options.property) { - console.warn("LiteGraph addWidget(...) without a callback or property assigned"); - } - if (type == "combo" && !w.options.values) { - throw "LiteGraph addWidget('combo',...) requires to pass values in options: { values:['red','blue'] }"; - } - this.widgets.push(w); - this.size = this.computeSize(); - return w; - }; - - LGraphNode.prototype.addCustomWidget = function(custom_widget) { - if (!this.widgets) { - this.widgets = []; - } - this.widgets.push(custom_widget); - return custom_widget; - }; - - /** - * returns the bounding of the object, used for rendering purposes - * bounding is: [topleft_cornerx, topleft_cornery, width, height] - * @method getBounding - * @return {Float32Array[4]} the total size - */ - LGraphNode.prototype.getBounding = function(out) { - out = out || new Float32Array(4); - out[0] = this.pos[0] - 4; - out[1] = this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT; - out[2] = this.size[0] + 4; - out[3] = this.size[1] + LiteGraph.NODE_TITLE_HEIGHT; - - if (this.onBounding) { - this.onBounding(out); - } - return out; - }; - - /** - * checks if a point is inside the shape of a node - * @method isPointInside - * @param {number} x - * @param {number} y - * @return {boolean} - */ - LGraphNode.prototype.isPointInside = function(x, y, margin, skip_title) { - margin = margin || 0; - - var margin_top = this.graph && this.graph.isLive() ? 0 : LiteGraph.NODE_TITLE_HEIGHT; - if (skip_title) { - margin_top = 0; - } - if (this.flags && this.flags.collapsed) { - //if ( distance([x,y], [this.pos[0] + this.size[0]*0.5, this.pos[1] + this.size[1]*0.5]) < LiteGraph.NODE_COLLAPSED_RADIUS) - if ( - isInsideRectangle( - x, - y, - this.pos[0] - margin, - this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT - margin, - (this._collapsed_width || LiteGraph.NODE_COLLAPSED_WIDTH) + - 2 * margin, - LiteGraph.NODE_TITLE_HEIGHT + 2 * margin - ) - ) { - return true; - } - } else if ( - this.pos[0] - 4 - margin < x && - this.pos[0] + this.size[0] + 4 + margin > x && - this.pos[1] - margin_top - margin < y && - this.pos[1] + this.size[1] + margin > y - ) { - return true; - } - return false; - }; - - /** - * checks if a point is inside a node slot, and returns info about which slot - * @method getSlotInPosition - * @param {number} x - * @param {number} y - * @return {Object} if found the object contains { input|output: slot object, slot: number, link_pos: [x,y] } - */ - LGraphNode.prototype.getSlotInPosition = function(x, y) { - //search for inputs - var link_pos = new Float32Array(2); - if (this.inputs) { - for (var i = 0, l = this.inputs.length; i < l; ++i) { - var input = this.inputs[i]; - this.getConnectionPos(true, i, link_pos); - if ( - isInsideRectangle( - x, - y, - link_pos[0] - 10, - link_pos[1] - 5, - 20, - 10 - ) - ) { - return { input: input, slot: i, link_pos: link_pos }; - } - } - } - - if (this.outputs) { - for (var i = 0, l = this.outputs.length; i < l; ++i) { - var output = this.outputs[i]; - this.getConnectionPos(false, i, link_pos); - if ( - isInsideRectangle( - x, - y, - link_pos[0] - 10, - link_pos[1] - 5, - 20, - 10 - ) - ) { - return { output: output, slot: i, link_pos: link_pos }; - } - } - } - - return null; - }; - - /** - * returns the input slot with a given name (used for dynamic slots), -1 if not found - * @method findInputSlot - * @param {string} name the name of the slot - * @return {number} the slot (-1 if not found) - */ - LGraphNode.prototype.findInputSlot = function(name) { - if (!this.inputs) { - return -1; - } - for (var i = 0, l = this.inputs.length; i < l; ++i) { - if (name == this.inputs[i].name) { - return i; - } - } - return -1; - }; - - /** - * returns the output slot with a given name (used for dynamic slots), -1 if not found - * @method findOutputSlot - * @param {string} name the name of the slot - * @return {number} the slot (-1 if not found) - */ - LGraphNode.prototype.findOutputSlot = function(name) { - if (!this.outputs) { - return -1; - } - for (var i = 0, l = this.outputs.length; i < l; ++i) { - if (name == this.outputs[i].name) { - return i; - } - } - return -1; - }; - - /** - * connect this node output to the input of another node - * @method connect - * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot) - * @param {LGraphNode} node the target node - * @param {number_or_string} target_slot the input slot of the target node (could be the number of the slot or the string with the name of the slot, or -1 to connect a trigger) - * @return {Object} the link_info is created, otherwise null - */ - LGraphNode.prototype.connect = function(slot, target_node, target_slot) { - target_slot = target_slot || 0; - - if (!this.graph) { - //could be connected before adding it to a graph - console.log( - "Connect: Error, node doesn't belong to any graph. Nodes must be added first to a graph before connecting them." - ); //due to link ids being associated with graphs - return null; - } - - //seek for the output slot - if (slot.constructor === String) { - slot = this.findOutputSlot(slot); - if (slot == -1) { - if (LiteGraph.debug) { - console.log("Connect: Error, no slot of name " + slot); - } - return null; - } - } else if (!this.outputs || slot >= this.outputs.length) { - if (LiteGraph.debug) { - console.log("Connect: Error, slot number not found"); - } - return null; - } - - if (target_node && target_node.constructor === Number) { - target_node = this.graph.getNodeById(target_node); - } - if (!target_node) { - throw "target node is null"; - } - - //avoid loopback - if (target_node == this) { - return null; - } - - //you can specify the slot by name - if (target_slot.constructor === String) { - target_slot = target_node.findInputSlot(target_slot); - if (target_slot == -1) { - if (LiteGraph.debug) { - console.log( - "Connect: Error, no slot of name " + target_slot - ); - } - return null; - } - } else if (target_slot === LiteGraph.EVENT) { - //search for first slot with event? - /* - //create input for trigger - var input = target_node.addInput("onTrigger", LiteGraph.EVENT ); - target_slot = target_node.inputs.length - 1; //last one is the one created - target_node.mode = LiteGraph.ON_TRIGGER; - */ - return null; - } else if ( - !target_node.inputs || - target_slot >= target_node.inputs.length - ) { - if (LiteGraph.debug) { - console.log("Connect: Error, slot number not found"); - } - return null; - } - - //if there is something already plugged there, disconnect - if (target_node.inputs[target_slot].link != null) { - target_node.disconnectInput(target_slot); - } - - //why here?? - //this.setDirtyCanvas(false,true); - //this.graph.connectionChange( this ); - - var output = this.outputs[slot]; - - //allows nodes to block connection - if (target_node.onConnectInput) { - if ( - target_node.onConnectInput(target_slot, output.type, output) === - false - ) { - return null; - } - } - - var input = target_node.inputs[target_slot]; - var link_info = null; - - if (LiteGraph.isValidConnection(output.type, input.type)) { - link_info = new LLink( - ++this.graph.last_link_id, - input.type, - this.id, - slot, - target_node.id, - target_slot - ); - - //add to graph links list - this.graph.links[link_info.id] = link_info; - - //connect in output - if (output.links == null) { - output.links = []; - } - output.links.push(link_info.id); - //connect in input - target_node.inputs[target_slot].link = link_info.id; - if (this.graph) { - this.graph._version++; - } - if (this.onConnectionsChange) { - this.onConnectionsChange( - LiteGraph.OUTPUT, - slot, - true, - link_info, - output - ); - } //link_info has been created now, so its updated - if (target_node.onConnectionsChange) { - target_node.onConnectionsChange( - LiteGraph.INPUT, - target_slot, - true, - link_info, - input - ); - } - if (this.graph && this.graph.onNodeConnectionChange) { - this.graph.onNodeConnectionChange( - LiteGraph.INPUT, - target_node, - target_slot, - this, - slot - ); - this.graph.onNodeConnectionChange( - LiteGraph.OUTPUT, - this, - slot, - target_node, - target_slot - ); - } - } - - this.setDirtyCanvas(false, true); - this.graph.connectionChange(this, link_info); - - return link_info; - }; - - /** - * disconnect one output to an specific node - * @method disconnectOutput - * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot) - * @param {LGraphNode} target_node the target node to which this slot is connected [Optional, if not target_node is specified all nodes will be disconnected] - * @return {boolean} if it was disconnected successfully - */ - LGraphNode.prototype.disconnectOutput = function(slot, target_node) { - if (slot.constructor === String) { - slot = this.findOutputSlot(slot); - if (slot == -1) { - if (LiteGraph.debug) { - console.log("Connect: Error, no slot of name " + slot); - } - return false; - } - } else if (!this.outputs || slot >= this.outputs.length) { - if (LiteGraph.debug) { - console.log("Connect: Error, slot number not found"); - } - return false; - } - - //get output slot - var output = this.outputs[slot]; - if (!output || !output.links || output.links.length == 0) { - return false; - } - - //one of the output links in this slot - if (target_node) { - if (target_node.constructor === Number) { - target_node = this.graph.getNodeById(target_node); - } - if (!target_node) { - throw "Target Node not found"; - } - - for (var i = 0, l = output.links.length; i < l; i++) { - var link_id = output.links[i]; - var link_info = this.graph.links[link_id]; - - //is the link we are searching for... - if (link_info.target_id == target_node.id) { - output.links.splice(i, 1); //remove here - var input = target_node.inputs[link_info.target_slot]; - input.link = null; //remove there - delete this.graph.links[link_id]; //remove the link from the links pool - if (this.graph) { - this.graph._version++; - } - if (target_node.onConnectionsChange) { - target_node.onConnectionsChange( - LiteGraph.INPUT, - link_info.target_slot, - false, - link_info, - input - ); - } //link_info hasn't been modified so its ok - if (this.onConnectionsChange) { - this.onConnectionsChange( - LiteGraph.OUTPUT, - slot, - false, - link_info, - output - ); - } - if (this.graph && this.graph.onNodeConnectionChange) { - this.graph.onNodeConnectionChange( - LiteGraph.OUTPUT, - this, - slot - ); - } - if (this.graph && this.graph.onNodeConnectionChange) { - this.graph.onNodeConnectionChange( - LiteGraph.OUTPUT, - this, - slot - ); - this.graph.onNodeConnectionChange( - LiteGraph.INPUT, - target_node, - link_info.target_slot - ); - } - break; - } - } - } //all the links in this output slot - else { - for (var i = 0, l = output.links.length; i < l; i++) { - var link_id = output.links[i]; - var link_info = this.graph.links[link_id]; - if (!link_info) { - //bug: it happens sometimes - continue; - } - - var target_node = this.graph.getNodeById(link_info.target_id); - var input = null; - if (this.graph) { - this.graph._version++; - } - if (target_node) { - input = target_node.inputs[link_info.target_slot]; - input.link = null; //remove other side link - if (target_node.onConnectionsChange) { - target_node.onConnectionsChange( - LiteGraph.INPUT, - link_info.target_slot, - false, - link_info, - input - ); - } //link_info hasn't been modified so its ok - if (this.graph && this.graph.onNodeConnectionChange) { - this.graph.onNodeConnectionChange( - LiteGraph.INPUT, - target_node, - link_info.target_slot - ); - } - } - delete this.graph.links[link_id]; //remove the link from the links pool - if (this.onConnectionsChange) { - this.onConnectionsChange( - LiteGraph.OUTPUT, - slot, - false, - link_info, - output - ); - } - if (this.graph && this.graph.onNodeConnectionChange) { - this.graph.onNodeConnectionChange( - LiteGraph.OUTPUT, - this, - slot - ); - this.graph.onNodeConnectionChange( - LiteGraph.INPUT, - target_node, - link_info.target_slot - ); - } - } - output.links = null; - } - - this.setDirtyCanvas(false, true); - this.graph.connectionChange(this); - return true; - }; - - /** - * disconnect one input - * @method disconnectInput - * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot) - * @return {boolean} if it was disconnected successfully - */ - LGraphNode.prototype.disconnectInput = function(slot) { - //seek for the output slot - if (slot.constructor === String) { - slot = this.findInputSlot(slot); - if (slot == -1) { - if (LiteGraph.debug) { - console.log("Connect: Error, no slot of name " + slot); - } - return false; - } - } else if (!this.inputs || slot >= this.inputs.length) { - if (LiteGraph.debug) { - console.log("Connect: Error, slot number not found"); - } - return false; - } - - var input = this.inputs[slot]; - if (!input) { - return false; - } - - var link_id = this.inputs[slot].link; - this.inputs[slot].link = null; - - //remove other side - var link_info = this.graph.links[link_id]; - if (link_info) { - var target_node = this.graph.getNodeById(link_info.origin_id); - if (!target_node) { - return false; - } - - var output = target_node.outputs[link_info.origin_slot]; - if (!output || !output.links || output.links.length == 0) { - return false; - } - - //search in the inputs list for this link - for (var i = 0, l = output.links.length; i < l; i++) { - if (output.links[i] == link_id) { - output.links.splice(i, 1); - break; - } - } - - delete this.graph.links[link_id]; //remove from the pool - if (this.graph) { - this.graph._version++; - } - if (this.onConnectionsChange) { - this.onConnectionsChange( - LiteGraph.INPUT, - slot, - false, - link_info, - input - ); - } - if (target_node.onConnectionsChange) { - target_node.onConnectionsChange( - LiteGraph.OUTPUT, - i, - false, - link_info, - output - ); - } - if (this.graph && this.graph.onNodeConnectionChange) { - this.graph.onNodeConnectionChange( - LiteGraph.OUTPUT, - target_node, - i - ); - this.graph.onNodeConnectionChange(LiteGraph.INPUT, this, slot); - } - } - - this.setDirtyCanvas(false, true); - this.graph.connectionChange(this); - return true; - }; - - /** - * returns the center of a connection point in canvas coords - * @method getConnectionPos - * @param {boolean} is_input true if if a input slot, false if it is an output - * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot) - * @param {vec2} out [optional] a place to store the output, to free garbage - * @return {[x,y]} the position - **/ - LGraphNode.prototype.getConnectionPos = function( - is_input, - slot_number, - out - ) { - out = out || new Float32Array(2); - var num_slots = 0; - if (is_input && this.inputs) { - num_slots = this.inputs.length; - } - if (!is_input && this.outputs) { - num_slots = this.outputs.length; - } - - var offset = LiteGraph.NODE_SLOT_HEIGHT * 0.5; - - if (this.flags.collapsed) { - var w = this._collapsed_width || LiteGraph.NODE_COLLAPSED_WIDTH; - if (this.horizontal) { - out[0] = this.pos[0] + w * 0.5; - if (is_input) { - out[1] = this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT; - } else { - out[1] = this.pos[1]; - } - } else { - if (is_input) { - out[0] = this.pos[0]; - } else { - out[0] = this.pos[0] + w; - } - out[1] = this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT * 0.5; - } - return out; - } - - //weird feature that never got finished - if (is_input && slot_number == -1) { - out[0] = this.pos[0] + LiteGraph.NODE_TITLE_HEIGHT * 0.5; - out[1] = this.pos[1] + LiteGraph.NODE_TITLE_HEIGHT * 0.5; - return out; - } - - //hard-coded pos - if ( - is_input && - num_slots > slot_number && - this.inputs[slot_number].pos - ) { - out[0] = this.pos[0] + this.inputs[slot_number].pos[0]; - out[1] = this.pos[1] + this.inputs[slot_number].pos[1]; - return out; - } else if ( - !is_input && - num_slots > slot_number && - this.outputs[slot_number].pos - ) { - out[0] = this.pos[0] + this.outputs[slot_number].pos[0]; - out[1] = this.pos[1] + this.outputs[slot_number].pos[1]; - return out; - } - - //horizontal distributed slots - if (this.horizontal) { - out[0] = - this.pos[0] + (slot_number + 0.5) * (this.size[0] / num_slots); - if (is_input) { - out[1] = this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT; - } else { - out[1] = this.pos[1] + this.size[1]; - } - return out; - } - - //default vertical slots - if (is_input) { - out[0] = this.pos[0] + offset; - } else { - out[0] = this.pos[0] + this.size[0] + 1 - offset; - } - out[1] = - this.pos[1] + - (slot_number + 0.7) * LiteGraph.NODE_SLOT_HEIGHT + - (this.constructor.slot_start_y || 0); - return out; - }; - - /* Force align to grid */ - LGraphNode.prototype.alignToGrid = function() { - this.pos[0] = - LiteGraph.CANVAS_GRID_SIZE * - Math.round(this.pos[0] / LiteGraph.CANVAS_GRID_SIZE); - this.pos[1] = - LiteGraph.CANVAS_GRID_SIZE * - Math.round(this.pos[1] / LiteGraph.CANVAS_GRID_SIZE); - }; - - /* Console output */ - LGraphNode.prototype.trace = function(msg) { - if (!this.console) { - this.console = []; - } - this.console.push(msg); - if (this.console.length > LGraphNode.MAX_CONSOLE) { - this.console.shift(); - } - - this.graph.onNodeTrace(this, msg); - }; - - /* Forces to redraw or the main canvas (LGraphNode) or the bg canvas (links) */ - LGraphNode.prototype.setDirtyCanvas = function( - dirty_foreground, - dirty_background - ) { - if (!this.graph) { - return; - } - this.graph.sendActionToCanvas("setDirty", [ - dirty_foreground, - dirty_background - ]); - }; - - LGraphNode.prototype.loadImage = function(url) { - var img = new Image(); - img.src = LiteGraph.node_images_path + url; - img.ready = false; - - var that = this; - img.onload = function() { - this.ready = true; - that.setDirtyCanvas(true); - }; - return img; - }; - - //safe LGraphNode action execution (not sure if safe) - /* -LGraphNode.prototype.executeAction = function(action) -{ - if(action == "") return false; - - if( action.indexOf(";") != -1 || action.indexOf("}") != -1) - { - this.trace("Error: Action contains unsafe characters"); - return false; - } - - var tokens = action.split("("); - var func_name = tokens[0]; - if( typeof(this[func_name]) != "function") - { - this.trace("Error: Action not found on node: " + func_name); - return false; - } - - var code = action; - - try - { - var _foo = eval; - eval = null; - (new Function("with(this) { " + code + "}")).call(this); - eval = _foo; - } - catch (err) - { - this.trace("Error executing action {" + action + "} :" + err); - return false; - } - - return true; -} -*/ - - /* Allows to get onMouseMove and onMouseUp events even if the mouse is out of focus */ - LGraphNode.prototype.captureInput = function(v) { - if (!this.graph || !this.graph.list_of_graphcanvas) { - return; - } - - var list = this.graph.list_of_graphcanvas; - - for (var i = 0; i < list.length; ++i) { - var c = list[i]; - //releasing somebody elses capture?! - if (!v && c.node_capturing_input != this) { - continue; - } - - //change - c.node_capturing_input = v ? this : null; - } - }; - - /** - * Collapse the node to make it smaller on the canvas - * @method collapse - **/ - LGraphNode.prototype.collapse = function(force) { - this.graph._version++; - if (this.constructor.collapsable === false && !force) { - return; - } - if (!this.flags.collapsed) { - this.flags.collapsed = true; - } else { - this.flags.collapsed = false; - } - this.setDirtyCanvas(true, true); - }; - - /** - * Forces the node to do not move or realign on Z - * @method pin - **/ - - LGraphNode.prototype.pin = function(v) { - this.graph._version++; - if (v === undefined) { - this.flags.pinned = !this.flags.pinned; - } else { - this.flags.pinned = v; - } - }; - - LGraphNode.prototype.localToScreen = function(x, y, graphcanvas) { - return [ - (x + this.pos[0]) * graphcanvas.scale + graphcanvas.offset[0], - (y + this.pos[1]) * graphcanvas.scale + graphcanvas.offset[1] - ]; - }; - - function LGraphGroup(title) { - this._ctor(title); - } - - global.LGraphGroup = LiteGraph.LGraphGroup = LGraphGroup; - - LGraphGroup.prototype._ctor = function(title) { - this.title = title || "Group"; - this.font_size = 24; - this.color = LGraphCanvas.node_colors.pale_blue - ? LGraphCanvas.node_colors.pale_blue.groupcolor - : "#AAA"; - 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.graph = null; - - Object.defineProperty(this, "pos", { - set: function(v) { - if (!v || v.length < 2) { - return; - } - this._pos[0] = v[0]; - this._pos[1] = v[1]; - }, - get: function() { - return this._pos; - }, - enumerable: true - }); - - Object.defineProperty(this, "size", { - set: function(v) { - if (!v || v.length < 2) { - return; - } - this._size[0] = Math.max(140, v[0]); - this._size[1] = Math.max(80, v[1]); - }, - get: function() { - return this._size; - }, - enumerable: true - }); - }; - - LGraphGroup.prototype.configure = function(o) { - this.title = o.title; - this._bounding.set(o.bounding); - this.color = o.color; - this.font = o.font; - }; - - LGraphGroup.prototype.serialize = function() { - var b = this._bounding; - return { - title: this.title, - bounding: [ - Math.round(b[0]), - Math.round(b[1]), - Math.round(b[2]), - Math.round(b[3]) - ], - color: this.color, - font: this.font - }; - }; - - LGraphGroup.prototype.move = function(deltax, deltay, ignore_nodes) { - this._pos[0] += deltax; - this._pos[1] += deltay; - if (ignore_nodes) { - return; - } - for (var i = 0; i < this._nodes.length; ++i) { - var node = this._nodes[i]; - node.pos[0] += deltax; - node.pos[1] += deltay; - } - }; - - LGraphGroup.prototype.recomputeInsideNodes = function() { - this._nodes.length = 0; - var nodes = this.graph._nodes; - var node_bounding = new Float32Array(4); - - for (var i = 0; i < nodes.length; ++i) { - var node = nodes[i]; - node.getBounding(node_bounding); - if (!overlapBounding(this._bounding, node_bounding)) { - continue; - } //out of the visible area - this._nodes.push(node); - } - }; - - LGraphGroup.prototype.isPointInside = LGraphNode.prototype.isPointInside; - LGraphGroup.prototype.setDirtyCanvas = LGraphNode.prototype.setDirtyCanvas; - - //**************************************** - - //Scale and Offset - function DragAndScale(element, skip_events) { - this.offset = new Float32Array([0, 0]); - this.scale = 1; - this.max_scale = 10; - this.min_scale = 0.1; - this.onredraw = null; - this.enabled = true; - this.last_mouse = [0, 0]; - this.element = null; - this.visible_area = new Float32Array(4); - - if (element) { - this.element = element; - if (!skip_events) { - this.bindEvents(element); - } - } - } - - LiteGraph.DragAndScale = DragAndScale; - - DragAndScale.prototype.bindEvents = function(element) { - this.last_mouse = new Float32Array(2); - - this._binded_mouse_callback = this.onMouse.bind(this); - - element.addEventListener("mousedown", this._binded_mouse_callback); - element.addEventListener("mousemove", this._binded_mouse_callback); - - element.addEventListener( - "mousewheel", - this._binded_mouse_callback, - false - ); - element.addEventListener("wheel", this._binded_mouse_callback, false); - }; - - DragAndScale.prototype.computeVisibleArea = function() { - if (!this.element) { - this.visible_area[0] = this.visible_area[1] = this.visible_area[2] = this.visible_area[3] = 0; - return; - } - var width = this.element.width; - var height = this.element.height; - var startx = -this.offset[0]; - var starty = -this.offset[1]; - var endx = startx + width / this.scale; - var endy = starty + height / this.scale; - this.visible_area[0] = startx; - this.visible_area[1] = starty; - this.visible_area[2] = endx - startx; - this.visible_area[3] = endy - starty; - }; - - DragAndScale.prototype.onMouse = function(e) { - if (!this.enabled) { - return; - } - - var canvas = this.element; - var rect = canvas.getBoundingClientRect(); - var x = e.clientX - rect.left; - var y = e.clientY - rect.top; - e.canvasx = x; - e.canvasy = y; - e.dragging = this.dragging; - - var ignore = false; - if (this.onmouse) { - ignore = this.onmouse(e); - } - - if (e.type == "mousedown") { - this.dragging = true; - canvas.removeEventListener( - "mousemove", - this._binded_mouse_callback - ); - document.body.addEventListener( - "mousemove", - this._binded_mouse_callback - ); - document.body.addEventListener( - "mouseup", - this._binded_mouse_callback - ); - } else if (e.type == "mousemove") { - if (!ignore) { - var deltax = x - this.last_mouse[0]; - var deltay = y - this.last_mouse[1]; - if (this.dragging) { - this.mouseDrag(deltax, deltay); - } - } - } else if (e.type == "mouseup") { - this.dragging = false; - document.body.removeEventListener( - "mousemove", - this._binded_mouse_callback - ); - document.body.removeEventListener( - "mouseup", - this._binded_mouse_callback - ); - canvas.addEventListener("mousemove", this._binded_mouse_callback); - } else if ( - e.type == "mousewheel" || - e.type == "wheel" || - e.type == "DOMMouseScroll" - ) { - e.eventType = "mousewheel"; - if (e.type == "wheel") { - e.wheel = -e.deltaY; - } else { - e.wheel = - e.wheelDeltaY != null ? e.wheelDeltaY : e.detail * -60; - } - - //from stack overflow - e.delta = e.wheelDelta - ? e.wheelDelta / 40 - : e.deltaY - ? -e.deltaY / 3 - : 0; - this.changeDeltaScale(1.0 + e.delta * 0.05); - } - - this.last_mouse[0] = x; - this.last_mouse[1] = y; - - e.preventDefault(); - e.stopPropagation(); - return false; - }; - - DragAndScale.prototype.toCanvasContext = function(ctx) { - ctx.scale(this.scale, this.scale); - ctx.translate(this.offset[0], this.offset[1]); - }; - - DragAndScale.prototype.convertOffsetToCanvas = function(pos) { - //return [pos[0] / this.scale - this.offset[0], pos[1] / this.scale - this.offset[1]]; - return [ - (pos[0] + this.offset[0]) * this.scale, - (pos[1] + this.offset[1]) * this.scale - ]; - }; - - DragAndScale.prototype.convertCanvasToOffset = function(pos, out) { - out = out || [0, 0]; - out[0] = pos[0] / this.scale - this.offset[0]; - out[1] = pos[1] / this.scale - this.offset[1]; - return out; - }; - - DragAndScale.prototype.mouseDrag = function(x, y) { - this.offset[0] += x / this.scale; - this.offset[1] += y / this.scale; - - if (this.onredraw) { - this.onredraw(this); - } - }; - - DragAndScale.prototype.changeScale = function(value, zooming_center) { - if (value < this.min_scale) { - value = this.min_scale; - } else if (value > this.max_scale) { - value = this.max_scale; - } - - if (value == this.scale) { - return; - } - - if (!this.element) { - return; - } - - var rect = this.element.getBoundingClientRect(); - if (!rect) { - return; - } - - zooming_center = zooming_center || [ - rect.width * 0.5, - rect.height * 0.5 - ]; - var center = this.convertCanvasToOffset(zooming_center); - this.scale = value; - if (Math.abs(this.scale - 1) < 0.01) { - this.scale = 1; - } - - var new_center = this.convertCanvasToOffset(zooming_center); - var delta_offset = [ - new_center[0] - center[0], - new_center[1] - center[1] - ]; - - this.offset[0] += delta_offset[0]; - this.offset[1] += delta_offset[1]; - - if (this.onredraw) { - this.onredraw(this); - } - }; - - DragAndScale.prototype.changeDeltaScale = function(value, zooming_center) { - this.changeScale(this.scale * value, zooming_center); - }; - - DragAndScale.prototype.reset = function() { - this.scale = 1; - this.offset[0] = 0; - this.offset[1] = 0; - }; - - //********************************************************************************* - // LGraphCanvas: LGraph renderer CLASS - //********************************************************************************* - - /** - * This class is in charge of rendering one graph inside a canvas. And provides all the interaction required. - * Valid callbacks are: onNodeSelected, onNodeDeselected, onShowNodePanel, onNodeDblClicked - * - * @class LGraphCanvas - * @constructor - * @param {HTMLCanvas} canvas the canvas where you want to render (it accepts a selector in string format or the canvas element itself) - * @param {LGraph} graph [optional] - * @param {Object} options [optional] { skip_rendering, autoresize } - */ - function LGraphCanvas(canvas, graph, options) { - options = options || {}; - - //if(graph === undefined) - // throw ("No graph assigned"); - this.background_image = - "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQBJREFUeNrs1rEKwjAUhlETUkj3vP9rdmr1Ysammk2w5wdxuLgcMHyptfawuZX4pJSWZTnfnu/lnIe/jNNxHHGNn//HNbbv+4dr6V+11uF527arU7+u63qfa/bnmh8sWLBgwYJlqRf8MEptXPBXJXa37BSl3ixYsGDBMliwFLyCV/DeLIMFCxYsWLBMwSt4Be/NggXLYMGCBUvBK3iNruC9WbBgwYJlsGApeAWv4L1ZBgsWLFiwYJmCV/AK3psFC5bBggULloJX8BpdwXuzYMGCBctgwVLwCl7Be7MMFixYsGDBsu8FH1FaSmExVfAxBa/gvVmwYMGCZbBg/W4vAQYA5tRF9QYlv/QAAAAASUVORK5CYII="; - - if (canvas && canvas.constructor === String) { - canvas = document.querySelector(canvas); - } - - this.ds = new DragAndScale(); - this.zoom_modify_alpha = true; //otherwise it generates ugly patterns when scaling down too much - - this.title_text_font = "" + LiteGraph.NODE_TEXT_SIZE + "px Arial"; - this.inner_text_font = - "normal " + LiteGraph.NODE_SUBTEXT_SIZE + "px Arial"; - this.node_title_color = LiteGraph.NODE_TITLE_COLOR; - this.default_link_color = LiteGraph.LINK_COLOR; - this.default_connection_color = { - input_off: "#778", - input_on: "#7F7", - output_off: "#778", - output_on: "#7F7" - }; - - this.highquality_render = true; - this.use_gradients = false; //set to true to render titlebar with gradients - this.editor_alpha = 1; //used for transition - this.pause_rendering = false; - this.clear_background = true; - - this.read_only = false; //if set to true users cannot modify the graph - this.render_only_selected = true; - this.live_mode = false; - this.show_info = true; - this.allow_dragcanvas = true; - this.allow_dragnodes = true; - this.allow_interaction = true; //allow to control widgets, buttons, collapse, etc - this.allow_searchbox = true; - this.allow_reconnect_links = false; //allows to change a connection with having to redo it again - - this.drag_mode = false; - this.dragging_rectangle = null; - - this.filter = null; //allows to filter to only accept some type of nodes in a graph - - this.always_render_background = false; - this.render_shadows = true; - this.render_canvas_border = true; - this.render_connections_shadows = false; //too much cpu - this.render_connections_border = true; - this.render_curved_connections = false; - this.render_connection_arrows = false; - this.render_collapsed_slots = true; - this.render_execution_order = false; - this.render_title_colored = true; - this.render_link_tooltip = true; - - this.links_render_mode = LiteGraph.SPLINE_LINK; - - this.canvas_mouse = [0, 0]; //mouse in canvas graph coordinates, where 0,0 is the top-left corner of the blue rectangle - - //to personalize the search box - this.onSearchBox = null; - this.onSearchBoxSelection = null; - - //callbacks - this.onMouse = null; - this.onDrawBackground = null; //to render background objects (behind nodes and connections) in the canvas affected by transform - this.onDrawForeground = null; //to render foreground objects (above nodes and connections) in the canvas affected by transform - this.onDrawOverlay = null; //to render foreground objects not affected by transform (for GUIs) - this.onDrawLinkTooltip = null; //called when rendering a tooltip - this.onNodeMoved = null; //called after moving a node - this.onSelectionChange = null; //called if the selection changes - - this.connections_width = 3; - this.round_radius = 8; - - this.current_node = null; - this.node_widget = null; //used for widgets - this.over_link_center = null; - this.last_mouse_position = [0, 0]; - this.visible_area = this.ds.visible_area; - this.visible_links = []; - - //link canvas and graph - if (graph) { - graph.attachCanvas(this); - } - - this.setCanvas(canvas); - this.clear(); - - if (!options.skip_render) { - this.startRendering(); - } - - this.autoresize = options.autoresize; - } - - global.LGraphCanvas = LiteGraph.LGraphCanvas = LGraphCanvas; - - LGraphCanvas.link_type_colors = { - "-1": LiteGraph.EVENT_LINK_COLOR, - number: "#AAA", - node: "#DCA" - }; - LGraphCanvas.gradients = {}; //cache of gradients - - /** - * clears all the data inside - * - * @method clear - */ - LGraphCanvas.prototype.clear = function() { - this.frame = 0; - this.last_draw_time = 0; - this.render_time = 0; - this.fps = 0; - - //this.scale = 1; - //this.offset = [0,0]; - - this.dragging_rectangle = null; - - this.selected_nodes = {}; - this.selected_group = null; - - this.visible_nodes = []; - this.node_dragged = null; - this.node_over = null; - this.node_capturing_input = null; - this.connecting_node = null; - this.highlighted_links = {}; - - this.dirty_canvas = true; - this.dirty_bgcanvas = true; - this.dirty_area = null; - - this.node_in_panel = null; - this.node_widget = null; - - this.last_mouse = [0, 0]; - this.last_mouseclick = 0; - this.visible_area.set([0, 0, 0, 0]); - - if (this.onClear) { - this.onClear(); - } - }; - - /** - * assigns a graph, you can reassign graphs to the same canvas - * - * @method setGraph - * @param {LGraph} graph - */ - LGraphCanvas.prototype.setGraph = function(graph, skip_clear) { - if (this.graph == graph) { - return; - } - - if (!skip_clear) { - this.clear(); - } - - if (!graph && this.graph) { - this.graph.detachCanvas(this); - return; - } - - /* - if(this.graph) - this.graph.canvas = null; //remove old graph link to the canvas - this.graph = graph; - if(this.graph) - this.graph.canvas = this; - */ - graph.attachCanvas(this); - this.setDirty(true, true); - }; - - /** - * opens a graph contained inside a node in the current graph - * - * @method openSubgraph - * @param {LGraph} graph - */ - LGraphCanvas.prototype.openSubgraph = function(graph) { - if (!graph) { - throw "graph cannot be null"; - } - - if (this.graph == graph) { - throw "graph cannot be the same"; - } - - this.clear(); - - if (this.graph) { - if (!this._graph_stack) { - this._graph_stack = []; - } - this._graph_stack.push(this.graph); - } - - graph.attachCanvas(this); - this.setDirty(true, true); - }; - - /** - * closes a subgraph contained inside a node - * - * @method closeSubgraph - * @param {LGraph} assigns a graph - */ - LGraphCanvas.prototype.closeSubgraph = function() { - if (!this._graph_stack || this._graph_stack.length == 0) { - return; - } - var subgraph_node = this.graph._subgraph_node; - var graph = this._graph_stack.pop(); - this.selected_nodes = {}; - this.highlighted_links = {}; - graph.attachCanvas(this); - this.setDirty(true, true); - if (subgraph_node) { - this.centerOnNode(subgraph_node); - this.selectNodes([subgraph_node]); - } - }; - - /** - * returns the visualy active graph (in case there are more in the stack) - * @method getCurrentGraph - * @return {LGraph} the active graph - */ - LGraphCanvas.prototype.getCurrentGraph = function() { - return this.graph; - }; - - /** - * assigns a canvas - * - * @method setCanvas - * @param {Canvas} assigns a canvas (also accepts the ID of the element (not a selector) - */ - LGraphCanvas.prototype.setCanvas = function(canvas, skip_events) { - var that = this; - - if (canvas) { - if (canvas.constructor === String) { - canvas = document.getElementById(canvas); - if (!canvas) { - throw "Error creating LiteGraph canvas: Canvas not found"; - } - } - } - - if (canvas === this.canvas) { - return; - } - - if (!canvas && this.canvas) { - //maybe detach events from old_canvas - if (!skip_events) { - this.unbindEvents(); - } - } - - this.canvas = canvas; - this.ds.element = canvas; - - if (!canvas) { - return; - } - - //this.canvas.tabindex = "1000"; - canvas.className += " lgraphcanvas"; - canvas.data = this; - canvas.tabindex = "1"; //to allow key events - - //bg canvas: used for non changing stuff - this.bgcanvas = null; - if (!this.bgcanvas) { - this.bgcanvas = document.createElement("canvas"); - this.bgcanvas.width = this.canvas.width; - this.bgcanvas.height = this.canvas.height; - } - - if (canvas.getContext == null) { - if (canvas.localName != "canvas") { - throw "Element supplied for LGraphCanvas must be a element, you passed a " + - canvas.localName; - } - throw "This browser doesn't support Canvas"; - } - - var ctx = (this.ctx = canvas.getContext("2d")); - if (ctx == null) { - if (!canvas.webgl_enabled) { - console.warn( - "This canvas seems to be WebGL, enabling WebGL renderer" - ); - } - this.enableWebGL(); - } - - //input: (move and up could be unbinded) - this._mousemove_callback = this.processMouseMove.bind(this); - this._mouseup_callback = this.processMouseUp.bind(this); - - if (!skip_events) { - this.bindEvents(); - } - }; - - //used in some events to capture them - LGraphCanvas.prototype._doNothing = function doNothing(e) { - e.preventDefault(); - return false; - }; - LGraphCanvas.prototype._doReturnTrue = function doNothing(e) { - e.preventDefault(); - return true; - }; - - /** - * binds mouse, keyboard, touch and drag events to the canvas - * @method bindEvents - **/ - LGraphCanvas.prototype.bindEvents = function() { - if (this._events_binded) { - console.warn("LGraphCanvas: events already binded"); - return; - } - - var canvas = this.canvas; - var ref_window = this.getCanvasWindow(); - var document = ref_window.document; //hack used when moving canvas between windows - - this._mousedown_callback = this.processMouseDown.bind(this); - this._mousewheel_callback = this.processMouseWheel.bind(this); - - canvas.addEventListener("mousedown", this._mousedown_callback, true); //down do not need to store the binded - canvas.addEventListener("mousemove", this._mousemove_callback); - canvas.addEventListener("mousewheel", this._mousewheel_callback, false); - - canvas.addEventListener("contextmenu", this._doNothing); - canvas.addEventListener( - "DOMMouseScroll", - this._mousewheel_callback, - false - ); - - //touch events - //if( 'touchstart' in document.documentElement ) - { - canvas.addEventListener("touchstart", this.touchHandler, true); - canvas.addEventListener("touchmove", this.touchHandler, true); - canvas.addEventListener("touchend", this.touchHandler, true); - canvas.addEventListener("touchcancel", this.touchHandler, true); - } - - //Keyboard ****************** - this._key_callback = this.processKey.bind(this); - - canvas.addEventListener("keydown", this._key_callback, true); - document.addEventListener("keyup", this._key_callback, true); //in document, otherwise it doesn't fire keyup - - //Dropping Stuff over nodes ************************************ - this._ondrop_callback = this.processDrop.bind(this); - - canvas.addEventListener("dragover", this._doNothing, false); - canvas.addEventListener("dragend", this._doNothing, false); - canvas.addEventListener("drop", this._ondrop_callback, false); - canvas.addEventListener("dragenter", this._doReturnTrue, false); - - this._events_binded = true; - }; - - /** - * unbinds mouse events from the canvas - * @method unbindEvents - **/ - LGraphCanvas.prototype.unbindEvents = function() { - if (!this._events_binded) { - console.warn("LGraphCanvas: no events binded"); - return; - } - - var ref_window = this.getCanvasWindow(); - var document = ref_window.document; - - this.canvas.removeEventListener("mousedown", this._mousedown_callback); - this.canvas.removeEventListener( - "mousewheel", - this._mousewheel_callback - ); - this.canvas.removeEventListener( - "DOMMouseScroll", - this._mousewheel_callback - ); - this.canvas.removeEventListener("keydown", this._key_callback); - document.removeEventListener("keyup", this._key_callback); - this.canvas.removeEventListener("contextmenu", this._doNothing); - this.canvas.removeEventListener("drop", this._ondrop_callback); - this.canvas.removeEventListener("dragenter", this._doReturnTrue); - - this.canvas.removeEventListener("touchstart", this.touchHandler); - this.canvas.removeEventListener("touchmove", this.touchHandler); - this.canvas.removeEventListener("touchend", this.touchHandler); - this.canvas.removeEventListener("touchcancel", this.touchHandler); - - this._mousedown_callback = null; - this._mousewheel_callback = null; - this._key_callback = null; - this._ondrop_callback = null; - - this._events_binded = false; - }; - - LGraphCanvas.getFileExtension = function(url) { - var question = url.indexOf("?"); - if (question != -1) { - url = url.substr(0, question); - } - var point = url.lastIndexOf("."); - if (point == -1) { - return ""; - } - return url.substr(point + 1).toLowerCase(); - }; - - /** - * this function allows to render the canvas using WebGL instead of Canvas2D - * this is useful if you plant to render 3D objects inside your nodes, it uses litegl.js for webgl and canvas2DtoWebGL to emulate the Canvas2D calls in webGL - * @method enableWebGL - **/ - LGraphCanvas.prototype.enableWebGL = function() { - if (typeof GL === undefined) { - throw "litegl.js must be included to use a WebGL canvas"; - } - if (typeof enableWebGLCanvas === undefined) { - throw "webglCanvas.js must be included to use this feature"; - } - - this.gl = this.ctx = enableWebGLCanvas(this.canvas); - this.ctx.webgl = true; - this.bgcanvas = this.canvas; - this.bgctx = this.gl; - this.canvas.webgl_enabled = true; - - /* - GL.create({ canvas: this.bgcanvas }); - this.bgctx = enableWebGLCanvas( this.bgcanvas ); - window.gl = this.gl; - */ - }; - - /** - * marks as dirty the canvas, this way it will be rendered again - * - * @class LGraphCanvas - * @method setDirty - * @param {bool} fgcanvas if the foreground canvas is dirty (the one containing the nodes) - * @param {bool} bgcanvas if the background canvas is dirty (the one containing the wires) - */ - LGraphCanvas.prototype.setDirty = function(fgcanvas, bgcanvas) { - if (fgcanvas) { - this.dirty_canvas = true; - } - if (bgcanvas) { - this.dirty_bgcanvas = true; - } - }; - - /** - * Used to attach the canvas in a popup - * - * @method getCanvasWindow - * @return {window} returns the window where the canvas is attached (the DOM root node) - */ - LGraphCanvas.prototype.getCanvasWindow = function() { - if (!this.canvas) { - return window; - } - var doc = this.canvas.ownerDocument; - return doc.defaultView || doc.parentWindow; - }; - - /** - * starts rendering the content of the canvas when needed - * - * @method startRendering - */ - LGraphCanvas.prototype.startRendering = function() { - if (this.is_rendering) { - return; - } //already rendering - - this.is_rendering = true; - renderFrame.call(this); - - function renderFrame() { - if (!this.pause_rendering) { - this.draw(); - } - - var window = this.getCanvasWindow(); - if (this.is_rendering) { - window.requestAnimationFrame(renderFrame.bind(this)); - } - } - }; - - /** - * stops rendering the content of the canvas (to save resources) - * - * @method stopRendering - */ - LGraphCanvas.prototype.stopRendering = function() { - this.is_rendering = false; - /* - if(this.rendering_timer_id) - { - clearInterval(this.rendering_timer_id); - this.rendering_timer_id = null; - } - */ - }; - - /* LiteGraphCanvas input */ - - LGraphCanvas.prototype.processMouseDown = function(e) { - if (!this.graph) { - return; - } - - this.adjustMouseEvent(e); - - var ref_window = this.getCanvasWindow(); - var document = ref_window.document; - LGraphCanvas.active_canvas = this; - var that = this; - - //move mouse move event to the window in case it drags outside of the canvas - this.canvas.removeEventListener("mousemove", this._mousemove_callback); - ref_window.document.addEventListener( - "mousemove", - this._mousemove_callback, - true - ); //catch for the entire window - ref_window.document.addEventListener( - "mouseup", - this._mouseup_callback, - true - ); - - var node = this.graph.getNodeOnPos( - e.canvasX, - e.canvasY, - this.visible_nodes, - 5 - ); - var skip_dragging = false; - var skip_action = false; - var now = LiteGraph.getTime(); - var is_double_click = now - this.last_mouseclick < 300; - - this.canvas_mouse[0] = e.canvasX; - this.canvas_mouse[1] = e.canvasY; - this.canvas.focus(); - - LiteGraph.closeAllContextMenus(ref_window); - - if (this.onMouse) { - if (this.onMouse(e) == true) { - return; - } - } - - if (e.which == 1) { - //left button mouse - if (e.ctrlKey) { - this.dragging_rectangle = new Float32Array(4); - this.dragging_rectangle[0] = e.canvasX; - this.dragging_rectangle[1] = e.canvasY; - this.dragging_rectangle[2] = 1; - this.dragging_rectangle[3] = 1; - skip_action = true; - } - - var clicking_canvas_bg = false; - - //when clicked on top of a node - //and it is not interactive - if (node && this.allow_interaction && !skip_action && !this.read_only) { - if (!this.live_mode && !node.flags.pinned) { - this.bringToFront(node); - } //if it wasn't selected? - - //not dragging mouse to connect two slots - if ( - !this.connecting_node && - !node.flags.collapsed && - !this.live_mode - ) { - //Search for corner for resize - if ( - !skip_action && - node.resizable !== false && - isInsideRectangle( - e.canvasX, - e.canvasY, - node.pos[0] + node.size[0] - 5, - node.pos[1] + node.size[1] - 5, - 10, - 10 - ) - ) { - this.resizing_node = node; - this.canvas.style.cursor = "se-resize"; - skip_action = true; - } else { - //search for outputs - if (node.outputs) { - for ( - var i = 0, l = node.outputs.length; - i < l; - ++i - ) { - var output = node.outputs[i]; - var link_pos = node.getConnectionPos(false, i); - if ( - isInsideRectangle( - e.canvasX, - e.canvasY, - link_pos[0] - 15, - link_pos[1] - 10, - 30, - 20 - ) - ) { - this.connecting_node = node; - this.connecting_output = output; - this.connecting_pos = node.getConnectionPos( false, i ); - this.connecting_slot = i; - - if (e.shiftKey) { - node.disconnectOutput(i); - } - - if (is_double_click) { - if (node.onOutputDblClick) { - node.onOutputDblClick(i, e); - } - } else { - if (node.onOutputClick) { - node.onOutputClick(i, e); - } - } - - skip_action = true; - break; - } - } - } - - //search for inputs - if (node.inputs) { - for ( - var i = 0, l = node.inputs.length; - i < l; - ++i - ) { - var input = node.inputs[i]; - var link_pos = node.getConnectionPos(true, i); - if ( - isInsideRectangle( - e.canvasX, - e.canvasY, - link_pos[0] - 15, - link_pos[1] - 10, - 30, - 20 - ) - ) { - if (is_double_click) { - if (node.onInputDblClick) { - node.onInputDblClick(i, e); - } - } else { - if (node.onInputClick) { - node.onInputClick(i, e); - } - } - - if (input.link !== null) { - var link_info = this.graph.links[ - input.link - ]; //before disconnecting - node.disconnectInput(i); - - if ( - this.allow_reconnect_links || - e.shiftKey - ) { - this.connecting_node = this.graph._nodes_by_id[ - link_info.origin_id - ]; - this.connecting_slot = - link_info.origin_slot; - this.connecting_output = this.connecting_node.outputs[ - this.connecting_slot - ]; - this.connecting_pos = this.connecting_node.getConnectionPos( false, this.connecting_slot ); - } - - this.dirty_bgcanvas = true; - skip_action = true; - } - } - } - } - } //not resizing - } - - //Search for corner for collapsing - /* - if( !skip_action && isInsideRectangle( e.canvasX, e.canvasY, node.pos[0], node.pos[1] - LiteGraph.NODE_TITLE_HEIGHT, LiteGraph.NODE_TITLE_HEIGHT, LiteGraph.NODE_TITLE_HEIGHT )) - { - node.collapse(); - skip_action = true; - } - */ - - //it wasn't clicked on the links boxes - if (!skip_action) { - var block_drag_node = false; - - //widgets - var widget = this.processNodeWidgets( node, this.canvas_mouse, e ); - if (widget) { - block_drag_node = true; - this.node_widget = [node, widget]; - } - - //double clicking - if (is_double_click && this.selected_nodes[node.id]) { - //double click node - if (node.onDblClick) { - node.onDblClick( e, [ e.canvasX - node.pos[0], e.canvasY - node.pos[1] ], this ); - } - this.processNodeDblClicked(node); - block_drag_node = true; - } - - //if do not capture mouse - if ( node.onMouseDown && node.onMouseDown( e, [e.canvasX - node.pos[0], e.canvasY - node.pos[1]], this ) ) { - block_drag_node = true; - } else if (this.live_mode) { - clicking_canvas_bg = true; - block_drag_node = true; - } - - if (!block_drag_node) { - if (this.allow_dragnodes) { - this.node_dragged = node; - } - if (!this.selected_nodes[node.id]) { - this.processNodeSelected(node, e); - } - } - - this.dirty_canvas = true; - } - } //clicked outside of nodes - else { - //search for link connector - if(!this.read_only) - for (var i = 0; i < this.visible_links.length; ++i) { - var link = this.visible_links[i]; - var center = link._pos; - if ( - !center || - e.canvasX < center[0] - 4 || - e.canvasX > center[0] + 4 || - e.canvasY < center[1] - 4 || - e.canvasY > center[1] + 4 - ) { - continue; - } - //link clicked - this.showLinkMenu(link, e); - this.over_link_center = null; //clear tooltip - break; - } - - this.selected_group = this.graph.getGroupOnPos( e.canvasX, e.canvasY ); - this.selected_group_resizing = false; - if (this.selected_group && !this.read_only ) { - if (e.ctrlKey) { - this.dragging_rectangle = null; - } - - var dist = distance( [e.canvasX, e.canvasY], [ this.selected_group.pos[0] + this.selected_group.size[0], this.selected_group.pos[1] + this.selected_group.size[1] ] ); - if (dist * this.ds.scale < 10) { - this.selected_group_resizing = true; - } else { - this.selected_group.recomputeInsideNodes(); - } - } - - if (is_double_click && !this.read_only && this.allow_searchbox) { - this.showSearchBox(e); - } - - clicking_canvas_bg = true; - } - - if (!skip_action && clicking_canvas_bg && this.allow_dragcanvas) { - this.dragging_canvas = true; - } - } else if (e.which == 2) { - //middle button - } else if (e.which == 3) { - //right button - if(!this.read_only) - this.processContextMenu(node, e); - } - - //TODO - //if(this.node_selected != prev_selected) - // this.onNodeSelectionChange(this.node_selected); - - this.last_mouse[0] = e.localX; - this.last_mouse[1] = e.localY; - this.last_mouseclick = LiteGraph.getTime(); - this.last_mouse_dragging = true; - - /* - if( (this.dirty_canvas || this.dirty_bgcanvas) && this.rendering_timer_id == null) - this.draw(); - */ - - this.graph.change(); - - //this is to ensure to defocus(blur) if a text input element is on focus - if ( - !ref_window.document.activeElement || - (ref_window.document.activeElement.nodeName.toLowerCase() != - "input" && - ref_window.document.activeElement.nodeName.toLowerCase() != - "textarea") - ) { - e.preventDefault(); - } - e.stopPropagation(); - - if (this.onMouseDown) { - this.onMouseDown(e); - } - - return false; - }; - - /** - * Called when a mouse move event has to be processed - * @method processMouseMove - **/ - LGraphCanvas.prototype.processMouseMove = function(e) { - if (this.autoresize) { - this.resize(); - } - - if (!this.graph) { - return; - } - - LGraphCanvas.active_canvas = this; - this.adjustMouseEvent(e); - var mouse = [e.localX, e.localY]; - var delta = [ - mouse[0] - this.last_mouse[0], - mouse[1] - this.last_mouse[1] - ]; - this.last_mouse = mouse; - this.canvas_mouse[0] = e.canvasX; - this.canvas_mouse[1] = e.canvasY; - e.dragging = this.last_mouse_dragging; - - if (this.node_widget) { - this.processNodeWidgets( - this.node_widget[0], - this.canvas_mouse, - e, - this.node_widget[1] - ); - this.dirty_canvas = true; - } - - if (this.dragging_rectangle) { - this.dragging_rectangle[2] = e.canvasX - this.dragging_rectangle[0]; - this.dragging_rectangle[3] = e.canvasY - this.dragging_rectangle[1]; - this.dirty_canvas = true; - } else if (this.selected_group && !this.read_only) { - //moving/resizing a group - if (this.selected_group_resizing) { - this.selected_group.size = [ - e.canvasX - this.selected_group.pos[0], - e.canvasY - this.selected_group.pos[1] - ]; - } else { - var deltax = delta[0] / this.ds.scale; - var deltay = delta[1] / this.ds.scale; - this.selected_group.move(deltax, deltay, e.ctrlKey); - if (this.selected_group._nodes.length) { - this.dirty_canvas = true; - } - } - this.dirty_bgcanvas = true; - } else if (this.dragging_canvas) { - this.ds.offset[0] += delta[0] / this.ds.scale; - this.ds.offset[1] += delta[1] / this.ds.scale; - this.dirty_canvas = true; - this.dirty_bgcanvas = true; - } else if (this.allow_interaction && !this.read_only) { - if (this.connecting_node) { - this.dirty_canvas = true; - } - - //get node over - var node = this.graph.getNodeOnPos( - e.canvasX, - e.canvasY, - this.visible_nodes - ); - - //remove mouseover flag - for (var i = 0, l = this.graph._nodes.length; i < l; ++i) { - if ( - this.graph._nodes[i].mouseOver && - node != this.graph._nodes[i] - ) { - //mouse leave - this.graph._nodes[i].mouseOver = false; - if (this.node_over && this.node_over.onMouseLeave) { - this.node_over.onMouseLeave(e); - } - this.node_over = null; - this.dirty_canvas = true; - } - } - - //mouse over a node - if (node) { - //this.canvas.style.cursor = "move"; - if (!node.mouseOver) { - //mouse enter - node.mouseOver = true; - this.node_over = node; - this.dirty_canvas = true; - - if (node.onMouseEnter) { - node.onMouseEnter(e); - } - } - - //in case the node wants to do something - if (node.onMouseMove) { - node.onMouseMove( - e, - [e.canvasX - node.pos[0], e.canvasY - node.pos[1]], - this - ); - } - - //if dragging a link - if (this.connecting_node) { - var pos = this._highlight_input || [0, 0]; //to store the output of isOverNodeInput - - //on top of input - if (this.isOverNodeBox(node, e.canvasX, e.canvasY)) { - //mouse on top of the corner box, don't know what to do - } else { - //check if I have a slot below de mouse - var slot = this.isOverNodeInput( - node, - e.canvasX, - e.canvasY, - pos - ); - if (slot != -1 && node.inputs[slot]) { - var slot_type = node.inputs[slot].type; - if ( - LiteGraph.isValidConnection( - this.connecting_output.type, - slot_type - ) - ) { - this._highlight_input = pos; - } - } else { - this._highlight_input = null; - } - } - } - - //Search for corner - if (this.canvas) { - if ( - isInsideRectangle( - e.canvasX, - e.canvasY, - node.pos[0] + node.size[0] - 5, - node.pos[1] + node.size[1] - 5, - 5, - 5 - ) - ) { - this.canvas.style.cursor = "se-resize"; - } else { - this.canvas.style.cursor = "crosshair"; - } - } - } else { //outside - - //search for link connector - var over_link = null; - for (var i = 0; i < this.visible_links.length; ++i) { - var link = this.visible_links[i]; - var center = link._pos; - if ( - !center || - e.canvasX < center[0] - 4 || - e.canvasX > center[0] + 4 || - e.canvasY < center[1] - 4 || - e.canvasY > center[1] + 4 - ) { - continue; - } - over_link = link; - break; - } - if( over_link != this.over_link_center ) - { - this.over_link_center = over_link; - this.dirty_canvas = true; - } - - if (this.canvas) { - this.canvas.style.cursor = ""; - } - } - - if ( this.node_capturing_input && this.node_capturing_input != node && this.node_capturing_input.onMouseMove ) { - this.node_capturing_input.onMouseMove(e,[e.canvasX - this.node_capturing_input.pos[0],e.canvasY - this.node_capturing_input.pos[1]], this); - } - - if (this.node_dragged && !this.live_mode) { - for (var i in this.selected_nodes) { - var n = this.selected_nodes[i]; - n.pos[0] += delta[0] / this.ds.scale; - n.pos[1] += delta[1] / this.ds.scale; - } - - this.dirty_canvas = true; - this.dirty_bgcanvas = true; - } - - if (this.resizing_node && !this.live_mode) { - //convert mouse to node space - this.resizing_node.size[0] = e.canvasX - this.resizing_node.pos[0]; - this.resizing_node.size[1] = e.canvasY - this.resizing_node.pos[1]; - - //constraint size - var max_slots = Math.max( - this.resizing_node.inputs ? this.resizing_node.inputs.length : 0, - this.resizing_node.outputs ? this.resizing_node.outputs.length : 0 - ); - var min_height = - max_slots * LiteGraph.NODE_SLOT_HEIGHT + - (this.resizing_node.widgets ? this.resizing_node.widgets.length : 0) * (LiteGraph.NODE_WIDGET_HEIGHT + 4) + 4; - if (this.resizing_node.size[1] < min_height) { - this.resizing_node.size[1] = min_height; - } - if (this.resizing_node.size[0] < LiteGraph.NODE_MIN_WIDTH) { - this.resizing_node.size[0] = LiteGraph.NODE_MIN_WIDTH; - } - - this.canvas.style.cursor = "se-resize"; - this.dirty_canvas = true; - this.dirty_bgcanvas = true; - } - } - - e.preventDefault(); - return false; - }; - - /** - * Called when a mouse up event has to be processed - * @method processMouseUp - **/ - LGraphCanvas.prototype.processMouseUp = function(e) { - if (!this.graph) { - return; - } - - var window = this.getCanvasWindow(); - var document = window.document; - LGraphCanvas.active_canvas = this; - - //restore the mousemove event back to the canvas - document.removeEventListener( - "mousemove", - this._mousemove_callback, - true - ); - this.canvas.addEventListener( - "mousemove", - this._mousemove_callback, - true - ); - document.removeEventListener("mouseup", this._mouseup_callback, true); - - this.adjustMouseEvent(e); - var now = LiteGraph.getTime(); - e.click_time = now - this.last_mouseclick; - this.last_mouse_dragging = false; - - if (e.which == 1) { - //left button - this.node_widget = null; - - if (this.selected_group) { - var diffx = - this.selected_group.pos[0] - - Math.round(this.selected_group.pos[0]); - var diffy = - this.selected_group.pos[1] - - Math.round(this.selected_group.pos[1]); - this.selected_group.move(diffx, diffy, e.ctrlKey); - this.selected_group.pos[0] = Math.round( - this.selected_group.pos[0] - ); - this.selected_group.pos[1] = Math.round( - this.selected_group.pos[1] - ); - if (this.selected_group._nodes.length) { - this.dirty_canvas = true; - } - this.selected_group = null; - } - this.selected_group_resizing = false; - - if (this.dragging_rectangle) { - if (this.graph) { - var nodes = this.graph._nodes; - var node_bounding = new Float32Array(4); - this.deselectAllNodes(); - //compute bounding and flip if left to right - var w = Math.abs(this.dragging_rectangle[2]); - var h = Math.abs(this.dragging_rectangle[3]); - var startx = - this.dragging_rectangle[2] < 0 - ? this.dragging_rectangle[0] - w - : this.dragging_rectangle[0]; - var starty = - this.dragging_rectangle[3] < 0 - ? this.dragging_rectangle[1] - h - : this.dragging_rectangle[1]; - this.dragging_rectangle[0] = startx; - this.dragging_rectangle[1] = starty; - this.dragging_rectangle[2] = w; - this.dragging_rectangle[3] = h; - - //test against all nodes (not visible because the rectangle maybe start outside - var to_select = []; - for (var i = 0; i < nodes.length; ++i) { - var node = nodes[i]; - node.getBounding(node_bounding); - if ( - !overlapBounding( - this.dragging_rectangle, - node_bounding - ) - ) { - continue; - } //out of the visible area - to_select.push(node); - } - if (to_select.length) { - this.selectNodes(to_select); - } - } - this.dragging_rectangle = null; - } else if (this.connecting_node) { - //dragging a connection - this.dirty_canvas = true; - this.dirty_bgcanvas = true; - - var node = this.graph.getNodeOnPos( - e.canvasX, - e.canvasY, - this.visible_nodes - ); - - //node below mouse - if (node) { - if ( - this.connecting_output.type == LiteGraph.EVENT && - this.isOverNodeBox(node, e.canvasX, e.canvasY) - ) { - this.connecting_node.connect( - this.connecting_slot, - node, - LiteGraph.EVENT - ); - } else { - //slot below mouse? connect - var slot = this.isOverNodeInput( - node, - e.canvasX, - e.canvasY - ); - if (slot != -1) { - this.connecting_node.connect( - this.connecting_slot, - node, - slot - ); - } else { - //not on top of an input - var input = node.getInputInfo(0); - //auto connect - if ( - this.connecting_output.type == LiteGraph.EVENT - ) { - this.connecting_node.connect( - this.connecting_slot, - node, - LiteGraph.EVENT - ); - } else if ( - input && - !input.link && - LiteGraph.isValidConnection( - input.type && this.connecting_output.type - ) - ) { - this.connecting_node.connect( - this.connecting_slot, - node, - 0 - ); - } - } - } - } - - this.connecting_output = null; - this.connecting_pos = null; - this.connecting_node = null; - this.connecting_slot = -1; - } //not dragging connection - else if (this.resizing_node) { - this.dirty_canvas = true; - this.dirty_bgcanvas = true; - this.resizing_node = null; - } else if (this.node_dragged) { - //node being dragged? - var node = this.node_dragged; - if ( - node && - e.click_time < 300 && - isInsideRectangle( e.canvasX, e.canvasY, node.pos[0], node.pos[1] - LiteGraph.NODE_TITLE_HEIGHT, LiteGraph.NODE_TITLE_HEIGHT, LiteGraph.NODE_TITLE_HEIGHT ) - ) { - node.collapse(); - } - - this.dirty_canvas = true; - this.dirty_bgcanvas = true; - this.node_dragged.pos[0] = Math.round(this.node_dragged.pos[0]); - this.node_dragged.pos[1] = Math.round(this.node_dragged.pos[1]); - if (this.graph.config.align_to_grid) { - this.node_dragged.alignToGrid(); - } - if( this.onNodeMoved ) - this.onNodeMoved( this.node_dragged ); - this.node_dragged = null; - } //no node being dragged - else { - //get node over - var node = this.graph.getNodeOnPos( - e.canvasX, - e.canvasY, - this.visible_nodes - ); - - if (!node && e.click_time < 300) { - this.deselectAllNodes(); - } - - this.dirty_canvas = true; - this.dragging_canvas = false; - - if (this.node_over && this.node_over.onMouseUp) { - this.node_over.onMouseUp( e, [ e.canvasX - this.node_over.pos[0], e.canvasY - this.node_over.pos[1] ], this ); - } - if ( - this.node_capturing_input && - this.node_capturing_input.onMouseUp - ) { - this.node_capturing_input.onMouseUp(e, [ - e.canvasX - this.node_capturing_input.pos[0], - e.canvasY - this.node_capturing_input.pos[1] - ]); - } - } - } else if (e.which == 2) { - //middle button - //trace("middle"); - this.dirty_canvas = true; - this.dragging_canvas = false; - } else if (e.which == 3) { - //right button - //trace("right"); - this.dirty_canvas = true; - this.dragging_canvas = false; - } - - /* - if((this.dirty_canvas || this.dirty_bgcanvas) && this.rendering_timer_id == null) - this.draw(); - */ - - this.graph.change(); - - e.stopPropagation(); - e.preventDefault(); - return false; - }; - - /** - * Called when a mouse wheel event has to be processed - * @method processMouseWheel - **/ - LGraphCanvas.prototype.processMouseWheel = function(e) { - if (!this.graph || !this.allow_dragcanvas) { - return; - } - - var delta = e.wheelDeltaY != null ? e.wheelDeltaY : e.detail * -60; - - this.adjustMouseEvent(e); - - var scale = this.ds.scale; - - if (delta > 0) { - scale *= 1.1; - } else if (delta < 0) { - scale *= 1 / 1.1; - } - - //this.setZoom( scale, [ e.localX, e.localY ] ); - this.ds.changeScale(scale, [e.localX, e.localY]); - - this.graph.change(); - - e.preventDefault(); - return false; // prevent default - }; - - /** - * returns true if a position (in graph space) is on top of a node little corner box - * @method isOverNodeBox - **/ - LGraphCanvas.prototype.isOverNodeBox = function(node, canvasx, canvasy) { - var title_height = LiteGraph.NODE_TITLE_HEIGHT; - if ( - isInsideRectangle( - canvasx, - canvasy, - node.pos[0] + 2, - node.pos[1] + 2 - title_height, - title_height - 4, - title_height - 4 - ) - ) { - return true; - } - return false; - }; - - /** - * returns true if a position (in graph space) is on top of a node input slot - * @method isOverNodeInput - **/ - LGraphCanvas.prototype.isOverNodeInput = function( - node, - canvasx, - canvasy, - slot_pos - ) { - if (node.inputs) { - for (var i = 0, l = node.inputs.length; i < l; ++i) { - var input = node.inputs[i]; - var link_pos = node.getConnectionPos(true, i); - var is_inside = false; - if (node.horizontal) { - is_inside = isInsideRectangle( - canvasx, - canvasy, - link_pos[0] - 5, - link_pos[1] - 10, - 10, - 20 - ); - } else { - is_inside = isInsideRectangle( - canvasx, - canvasy, - link_pos[0] - 10, - link_pos[1] - 5, - 40, - 10 - ); - } - if (is_inside) { - if (slot_pos) { - slot_pos[0] = link_pos[0]; - slot_pos[1] = link_pos[1]; - } - return i; - } - } - } - return -1; - }; - - /** - * process a key event - * @method processKey - **/ - LGraphCanvas.prototype.processKey = function(e) { - if (!this.graph) { - return; - } - - var block_default = false; - //console.log(e); //debug - - if (e.target.localName == "input") { - return; - } - - if (e.type == "keydown") { - if (e.keyCode == 32) { - //esc - this.dragging_canvas = true; - block_default = true; - } - - //select all Control A - if (e.keyCode == 65 && e.ctrlKey) { - this.selectNodes(); - block_default = true; - } - - if (e.code == "KeyC" && (e.metaKey || e.ctrlKey) && !e.shiftKey) { - //copy - if (this.selected_nodes) { - this.copyToClipboard(); - block_default = true; - } - } - - if (e.code == "KeyV" && (e.metaKey || e.ctrlKey) && !e.shiftKey) { - //paste - this.pasteFromClipboard(); - } - - //delete or backspace - if (e.keyCode == 46 || e.keyCode == 8) { - if ( - e.target.localName != "input" && - e.target.localName != "textarea" - ) { - this.deleteSelectedNodes(); - block_default = true; - } - } - - //collapse - //... - - //TODO - if (this.selected_nodes) { - for (var i in this.selected_nodes) { - if (this.selected_nodes[i].onKeyDown) { - this.selected_nodes[i].onKeyDown(e); - } - } - } - } else if (e.type == "keyup") { - if (e.keyCode == 32) { - this.dragging_canvas = false; - } - - if (this.selected_nodes) { - for (var i in this.selected_nodes) { - if (this.selected_nodes[i].onKeyUp) { - this.selected_nodes[i].onKeyUp(e); - } - } - } - } - - this.graph.change(); - - if (block_default) { - e.preventDefault(); - e.stopImmediatePropagation(); - return false; - } - }; - - LGraphCanvas.prototype.copyToClipboard = function() { - var clipboard_info = { - nodes: [], - links: [] - }; - var index = 0; - var selected_nodes_array = []; - for (var i in this.selected_nodes) { - var node = this.selected_nodes[i]; - node._relative_id = index; - selected_nodes_array.push(node); - index += 1; - } - - for (var i = 0; i < selected_nodes_array.length; ++i) { - var node = selected_nodes_array[i]; - var cloned = node.clone(); - if(!cloned) - { - console.warn("node type not found: " + node.type ); - continue; - } - clipboard_info.nodes.push(cloned.serialize()); - if (node.inputs && node.inputs.length) { - for (var j = 0; j < node.inputs.length; ++j) { - var input = node.inputs[j]; - if (!input || input.link == null) { - continue; - } - var link_info = this.graph.links[input.link]; - if (!link_info) { - continue; - } - var target_node = this.graph.getNodeById( - link_info.origin_id - ); - if (!target_node || !this.selected_nodes[target_node.id]) { - //improve this by allowing connections to non-selected nodes - continue; - } //not selected - clipboard_info.links.push([ - target_node._relative_id, - link_info.origin_slot, //j, - node._relative_id, - link_info.target_slot - ]); - } - } - } - localStorage.setItem( - "litegrapheditor_clipboard", - JSON.stringify(clipboard_info) - ); - }; - - LGraphCanvas.prototype.pasteFromClipboard = function() { - var data = localStorage.getItem("litegrapheditor_clipboard"); - if (!data) { - return; - } - - //create nodes - var clipboard_info = JSON.parse(data); - var nodes = []; - for (var i = 0; i < clipboard_info.nodes.length; ++i) { - var node_data = clipboard_info.nodes[i]; - var node = LiteGraph.createNode(node_data.type); - if (node) { - node.configure(node_data); - node.pos[0] += 5; - node.pos[1] += 5; - this.graph.add(node); - nodes.push(node); - } - } - - //create links - for (var i = 0; i < clipboard_info.links.length; ++i) { - var link_info = clipboard_info.links[i]; - var origin_node = nodes[link_info[0]]; - var target_node = nodes[link_info[2]]; - if( origin_node && target_node ) - origin_node.connect(link_info[1], target_node, link_info[3]); - else - console.warn("Warning, nodes missing on pasting"); - } - - this.selectNodes(nodes); - }; - - /** - * process a item drop event on top the canvas - * @method processDrop - **/ - LGraphCanvas.prototype.processDrop = function(e) { - e.preventDefault(); - this.adjustMouseEvent(e); - - var pos = [e.canvasX, e.canvasY]; - var node = this.graph.getNodeOnPos(pos[0], pos[1]); - - if (!node) { - var r = null; - if (this.onDropItem) { - r = this.onDropItem(event); - } - if (!r) { - this.checkDropItem(e); - } - return; - } - - if (node.onDropFile || node.onDropData) { - var files = e.dataTransfer.files; - if (files && files.length) { - for (var i = 0; i < files.length; i++) { - var file = e.dataTransfer.files[0]; - var filename = file.name; - var ext = LGraphCanvas.getFileExtension(filename); - //console.log(file); - - if (node.onDropFile) { - node.onDropFile(file); - } - - if (node.onDropData) { - //prepare reader - var reader = new FileReader(); - reader.onload = function(event) { - //console.log(event.target); - var data = event.target.result; - node.onDropData(data, filename, file); - }; - - //read data - var type = file.type.split("/")[0]; - if (type == "text" || type == "") { - reader.readAsText(file); - } else if (type == "image") { - reader.readAsDataURL(file); - } else { - reader.readAsArrayBuffer(file); - } - } - } - } - } - - if (node.onDropItem) { - if (node.onDropItem(event)) { - return true; - } - } - - if (this.onDropItem) { - return this.onDropItem(event); - } - - return false; - }; - - //called if the graph doesn't have a default drop item behaviour - LGraphCanvas.prototype.checkDropItem = function(e) { - if (e.dataTransfer.files.length) { - var file = e.dataTransfer.files[0]; - var ext = LGraphCanvas.getFileExtension(file.name).toLowerCase(); - var nodetype = LiteGraph.node_types_by_file_extension[ext]; - if (nodetype) { - var node = LiteGraph.createNode(nodetype.type); - node.pos = [e.canvasX, e.canvasY]; - this.graph.add(node); - if (node.onDropFile) { - node.onDropFile(file); - } - } - } - }; - - LGraphCanvas.prototype.processNodeDblClicked = function(n) { - if (this.onShowNodePanel) { - this.onShowNodePanel(n); - } - - if (this.onNodeDblClicked) { - this.onNodeDblClicked(n); - } - - this.setDirty(true); - }; - - LGraphCanvas.prototype.processNodeSelected = function(node, e) { - this.selectNode(node, e && e.shiftKey); - if (this.onNodeSelected) { - this.onNodeSelected(node); - } - }; - - /** - * selects a given node (or adds it to the current selection) - * @method selectNode - **/ - LGraphCanvas.prototype.selectNode = function( - node, - add_to_current_selection - ) { - if (node == null) { - this.deselectAllNodes(); - } else { - this.selectNodes([node], add_to_current_selection); - } - }; - - /** - * selects several nodes (or adds them to the current selection) - * @method selectNodes - **/ - LGraphCanvas.prototype.selectNodes = function( - nodes, - add_to_current_selection - ) { - if (!add_to_current_selection) { - this.deselectAllNodes(); - } - - nodes = nodes || this.graph._nodes; - for (var i = 0; i < nodes.length; ++i) { - var node = nodes[i]; - if (node.is_selected) { - continue; - } - - if (!node.is_selected && node.onSelected) { - node.onSelected(); - } - node.is_selected = true; - this.selected_nodes[node.id] = node; - - if (node.inputs) { - for (var j = 0; j < node.inputs.length; ++j) { - this.highlighted_links[node.inputs[j].link] = true; - } - } - if (node.outputs) { - for (var j = 0; j < node.outputs.length; ++j) { - var out = node.outputs[j]; - if (out.links) { - for (var k = 0; k < out.links.length; ++k) { - this.highlighted_links[out.links[k]] = true; - } - } - } - } - } - - if( this.onSelectionChange ) - this.onSelectionChange( this.selected_nodes ); - - this.setDirty(true); - }; - - /** - * removes a node from the current selection - * @method deselectNode - **/ - LGraphCanvas.prototype.deselectNode = function(node) { - if (!node.is_selected) { - return; - } - if (node.onDeselected) { - node.onDeselected(); - } - node.is_selected = false; - - if (this.onNodeDeselected) { - this.onNodeDeselected(node); - } - - //remove highlighted - if (node.inputs) { - for (var i = 0; i < node.inputs.length; ++i) { - delete this.highlighted_links[node.inputs[i].link]; - } - } - if (node.outputs) { - for (var i = 0; i < node.outputs.length; ++i) { - var out = node.outputs[i]; - if (out.links) { - for (var j = 0; j < out.links.length; ++j) { - delete this.highlighted_links[out.links[j]]; - } - } - } - } - }; - - /** - * removes all nodes from the current selection - * @method deselectAllNodes - **/ - LGraphCanvas.prototype.deselectAllNodes = function() { - if (!this.graph) { - return; - } - var nodes = this.graph._nodes; - for (var i = 0, l = nodes.length; i < l; ++i) { - var node = nodes[i]; - if (!node.is_selected) { - continue; - } - if (node.onDeselected) { - node.onDeselected(); - } - node.is_selected = false; - if (this.onNodeDeselected) { - this.onNodeDeselected(node); - } - } - this.selected_nodes = {}; - this.current_node = null; - this.highlighted_links = {}; - if( this.onSelectionChange ) - this.onSelectionChange( this.selected_nodes ); - this.setDirty(true); - }; - - /** - * deletes all nodes in the current selection from the graph - * @method deleteSelectedNodes - **/ - LGraphCanvas.prototype.deleteSelectedNodes = function() { - for (var i in this.selected_nodes) { - var node = this.selected_nodes[i]; - //autoconnect when possible (very basic, only takes into account first input-output) - if(node.inputs && node.inputs.length && node.outputs && node.outputs.length && LiteGraph.isValidConnection( node.inputs[0].type, node.outputs[0].type ) && node.inputs[0].link && node.outputs[0].links && node.outputs[0].links.length ) - { - var input_link = node.graph.links[ node.inputs[0].link ]; - var output_link = node.graph.links[ node.outputs[0].links[0] ]; - var input_node = node.getInputNode(0); - var output_node = node.getOutputNodes(0)[0]; - if(input_node && output_node) - input_node.connect( input_link.origin_slot, output_node, output_link.target_slot ); - } - this.graph.remove(node); - if (this.onNodeDeselected) { - this.onNodeDeselected(node); - } - } - this.selected_nodes = {}; - this.current_node = null; - this.highlighted_links = {}; - this.setDirty(true); - }; - - /** - * centers the camera on a given node - * @method centerOnNode - **/ - LGraphCanvas.prototype.centerOnNode = function(node) { - this.ds.offset[0] = - -node.pos[0] - - node.size[0] * 0.5 + - (this.canvas.width * 0.5) / this.ds.scale; - this.ds.offset[1] = - -node.pos[1] - - node.size[1] * 0.5 + - (this.canvas.height * 0.5) / this.ds.scale; - this.setDirty(true, true); - }; - - /** - * adds some useful properties to a mouse event, like the position in graph coordinates - * @method adjustMouseEvent - **/ - LGraphCanvas.prototype.adjustMouseEvent = function(e) { - if (this.canvas) { - var b = this.canvas.getBoundingClientRect(); - e.localX = e.clientX - b.left; - e.localY = e.clientY - b.top; - } else { - e.localX = e.clientX; - e.localY = e.clientY; - } - - e.deltaX = e.localX - this.last_mouse_position[0]; - e.deltaY = e.localY - this.last_mouse_position[1]; - - this.last_mouse_position[0] = e.localX; - this.last_mouse_position[1] = e.localY; - - e.canvasX = e.localX / this.ds.scale - this.ds.offset[0]; - e.canvasY = e.localY / this.ds.scale - this.ds.offset[1]; - }; - - /** - * changes the zoom level of the graph (default is 1), you can pass also a place used to pivot the zoom - * @method setZoom - **/ - LGraphCanvas.prototype.setZoom = function(value, zooming_center) { - this.ds.changeScale(value, zooming_center); - /* - if(!zooming_center && this.canvas) - zooming_center = [this.canvas.width * 0.5,this.canvas.height * 0.5]; - - var center = this.convertOffsetToCanvas( zooming_center ); - - this.ds.scale = value; - - if(this.scale > this.max_zoom) - this.scale = this.max_zoom; - else if(this.scale < this.min_zoom) - this.scale = this.min_zoom; - - var new_center = this.convertOffsetToCanvas( zooming_center ); - var delta_offset = [new_center[0] - center[0], new_center[1] - center[1]]; - - this.offset[0] += delta_offset[0]; - this.offset[1] += delta_offset[1]; - */ - - this.dirty_canvas = true; - this.dirty_bgcanvas = true; - }; - - /** - * converts a coordinate from graph coordinates to canvas2D coordinates - * @method convertOffsetToCanvas - **/ - LGraphCanvas.prototype.convertOffsetToCanvas = function(pos, out) { - return this.ds.convertOffsetToCanvas(pos, out); - }; - - /** - * converts a coordinate from Canvas2D coordinates to graph space - * @method convertCanvasToOffset - **/ - LGraphCanvas.prototype.convertCanvasToOffset = function(pos, out) { - return this.ds.convertCanvasToOffset(pos, out); - }; - - //converts event coordinates from canvas2D to graph coordinates - LGraphCanvas.prototype.convertEventToCanvasOffset = function(e) { - var rect = this.canvas.getBoundingClientRect(); - return this.convertCanvasToOffset([ - e.clientX - rect.left, - e.clientY - rect.top - ]); - }; - - /** - * brings a node to front (above all other nodes) - * @method bringToFront - **/ - LGraphCanvas.prototype.bringToFront = function(node) { - var i = this.graph._nodes.indexOf(node); - if (i == -1) { - return; - } - - this.graph._nodes.splice(i, 1); - this.graph._nodes.push(node); - }; - - /** - * sends a node to the back (below all other nodes) - * @method sendToBack - **/ - LGraphCanvas.prototype.sendToBack = function(node) { - var i = this.graph._nodes.indexOf(node); - if (i == -1) { - return; - } - - this.graph._nodes.splice(i, 1); - this.graph._nodes.unshift(node); - }; - - /* Interaction */ - - /* LGraphCanvas render */ - var temp = new Float32Array(4); - - /** - * checks which nodes are visible (inside the camera area) - * @method computeVisibleNodes - **/ - LGraphCanvas.prototype.computeVisibleNodes = function(nodes, out) { - var visible_nodes = out || []; - visible_nodes.length = 0; - nodes = nodes || this.graph._nodes; - for (var i = 0, l = nodes.length; i < l; ++i) { - var n = nodes[i]; - - //skip rendering nodes in live mode - if (this.live_mode && !n.onDrawBackground && !n.onDrawForeground) { - continue; - } - - if (!overlapBounding(this.visible_area, n.getBounding(temp))) { - continue; - } //out of the visible area - - visible_nodes.push(n); - } - return visible_nodes; - }; - - /** - * renders the whole canvas content, by rendering in two separated canvas, one containing the background grid and the connections, and one containing the nodes) - * @method draw - **/ - LGraphCanvas.prototype.draw = function(force_canvas, force_bgcanvas) { - if (!this.canvas) { - return; - } - - //fps counting - var now = LiteGraph.getTime(); - this.render_time = (now - this.last_draw_time) * 0.001; - this.last_draw_time = now; - - if (this.graph) { - this.ds.computeVisibleArea(); - } - - if ( - this.dirty_bgcanvas || - force_bgcanvas || - this.always_render_background || - (this.graph && - this.graph._last_trigger_time && - now - this.graph._last_trigger_time < 1000) - ) { - this.drawBackCanvas(); - } - - if (this.dirty_canvas || force_canvas) { - this.drawFrontCanvas(); - } - - this.fps = this.render_time ? 1.0 / this.render_time : 0; - this.frame += 1; - }; - - /** - * draws the front canvas (the one containing all the nodes) - * @method drawFrontCanvas - **/ - LGraphCanvas.prototype.drawFrontCanvas = function() { - this.dirty_canvas = false; - - if (!this.ctx) { - this.ctx = this.bgcanvas.getContext("2d"); - } - var ctx = this.ctx; - if (!ctx) { - //maybe is using webgl... - return; - } - - if (ctx.start2D) { - ctx.start2D(); - } - - var canvas = this.canvas; - - //reset in case of error - ctx.restore(); - ctx.setTransform(1, 0, 0, 1, 0, 0); - - //clip dirty area if there is one, otherwise work in full canvas - if (this.dirty_area) { - ctx.save(); - ctx.beginPath(); - ctx.rect( - this.dirty_area[0], - this.dirty_area[1], - this.dirty_area[2], - this.dirty_area[3] - ); - ctx.clip(); - } - - //clear - //canvas.width = canvas.width; - if (this.clear_background) { - ctx.clearRect(0, 0, canvas.width, canvas.height); - } - - //draw bg canvas - if (this.bgcanvas == this.canvas) { - this.drawBackCanvas(); - } else { - ctx.drawImage(this.bgcanvas, 0, 0); - } - - //rendering - if (this.onRender) { - this.onRender(canvas, ctx); - } - - //info widget - if (this.show_info) { - this.renderInfo(ctx); - } - - if (this.graph) { - //apply transformations - ctx.save(); - this.ds.toCanvasContext(ctx); - - //draw nodes - var drawn_nodes = 0; - var visible_nodes = this.computeVisibleNodes( - null, - this.visible_nodes - ); - - for (var i = 0; i < visible_nodes.length; ++i) { - var node = visible_nodes[i]; - - //transform coords system - ctx.save(); - ctx.translate(node.pos[0], node.pos[1]); - - //Draw - this.drawNode(node, ctx); - drawn_nodes += 1; - - //Restore - ctx.restore(); - } - - //on top (debug) - if (this.render_execution_order) { - this.drawExecutionOrder(ctx); - } - - //connections ontop? - if (this.graph.config.links_ontop) { - if (!this.live_mode) { - this.drawConnections(ctx); - } - } - - //current connection (the one being dragged by the mouse) - if (this.connecting_pos != null) { - ctx.lineWidth = this.connections_width; - var link_color = null; - - switch (this.connecting_output.type) { - case LiteGraph.EVENT: - link_color = LiteGraph.EVENT_LINK_COLOR; - break; - default: - link_color = LiteGraph.CONNECTING_LINK_COLOR; - } - - //the connection being dragged by the mouse - this.renderLink( - ctx, - this.connecting_pos, - [this.canvas_mouse[0], this.canvas_mouse[1]], - null, - false, - null, - link_color, - this.connecting_output.dir || - (this.connecting_node.horizontal - ? LiteGraph.DOWN - : LiteGraph.RIGHT), - LiteGraph.CENTER - ); - - ctx.beginPath(); - if ( - this.connecting_output.type === LiteGraph.EVENT || - this.connecting_output.shape === LiteGraph.BOX_SHAPE - ) { - ctx.rect( - this.connecting_pos[0] - 6 + 0.5, - this.connecting_pos[1] - 5 + 0.5, - 14, - 10 - ); - } else { - ctx.arc( - this.connecting_pos[0], - this.connecting_pos[1], - 4, - 0, - Math.PI * 2 - ); - } - ctx.fill(); - - ctx.fillStyle = "#ffcc00"; - if (this._highlight_input) { - ctx.beginPath(); - ctx.arc( - this._highlight_input[0], - this._highlight_input[1], - 6, - 0, - Math.PI * 2 - ); - ctx.fill(); - } - } - - //the selection rectangle - if (this.dragging_rectangle) { - ctx.strokeStyle = "#FFF"; - ctx.strokeRect( - this.dragging_rectangle[0], - this.dragging_rectangle[1], - this.dragging_rectangle[2], - this.dragging_rectangle[3] - ); - } - - //on top of link center - if(this.over_link_center && this.render_link_tooltip) - this.drawLinkTooltip( ctx, this.over_link_center ); - else - if(this.onDrawLinkTooltip) //to remove - this.onDrawLinkTooltip(ctx,null); - - //custom info - if (this.onDrawForeground) { - this.onDrawForeground(ctx, this.visible_rect); - } - - ctx.restore(); - } - - if (this.onDrawOverlay) { - this.onDrawOverlay(ctx); - } - - if (this.dirty_area) { - ctx.restore(); - //this.dirty_area = null; - } - - if (ctx.finish2D) { - //this is a function I use in webgl renderer - ctx.finish2D(); - } - }; - - /** - * draws some useful stats in the corner of the canvas - * @method renderInfo - **/ - LGraphCanvas.prototype.renderInfo = function(ctx, x, y) { - x = x || 0; - y = y || 0; - - ctx.save(); - ctx.translate(x, y); - - ctx.font = "10px Arial"; - ctx.fillStyle = "#888"; - if (this.graph) { - ctx.fillText( "T: " + this.graph.globaltime.toFixed(2) + "s", 5, 13 * 1 ); - ctx.fillText("I: " + this.graph.iteration, 5, 13 * 2 ); - ctx.fillText("N: " + this.graph._nodes.length + " [" + this.visible_nodes.length + "]", 5, 13 * 3 ); - ctx.fillText("V: " + this.graph._version, 5, 13 * 4); - ctx.fillText("FPS:" + this.fps.toFixed(2), 5, 13 * 5); - } else { - ctx.fillText("No graph selected", 5, 13 * 1); - } - ctx.restore(); - }; - - /** - * draws the back canvas (the one containing the background and the connections) - * @method drawBackCanvas - **/ - LGraphCanvas.prototype.drawBackCanvas = function() { - var canvas = this.bgcanvas; - if ( - canvas.width != this.canvas.width || - canvas.height != this.canvas.height - ) { - canvas.width = this.canvas.width; - canvas.height = this.canvas.height; - } - - if (!this.bgctx) { - this.bgctx = this.bgcanvas.getContext("2d"); - } - var ctx = this.bgctx; - if (ctx.start) { - ctx.start(); - } - - //clear - if (this.clear_background) { - ctx.clearRect(0, 0, canvas.width, canvas.height); - } - - if (this._graph_stack && this._graph_stack.length) { - ctx.save(); - var parent_graph = this._graph_stack[this._graph_stack.length - 1]; - var subgraph_node = this.graph._subgraph_node; - ctx.strokeStyle = subgraph_node.bgcolor; - ctx.lineWidth = 10; - ctx.strokeRect(1, 1, canvas.width - 2, canvas.height - 2); - ctx.lineWidth = 1; - ctx.font = "40px Arial"; - ctx.textAlign = "center"; - ctx.fillStyle = subgraph_node.bgcolor || "#AAA"; - var title = ""; - for (var i = 1; i < this._graph_stack.length; ++i) { - title += - this._graph_stack[i]._subgraph_node.getTitle() + " >> "; - } - ctx.fillText( - title + subgraph_node.getTitle(), - canvas.width * 0.5, - 40 - ); - ctx.restore(); - } - - var bg_already_painted = false; - if (this.onRenderBackground) { - bg_already_painted = this.onRenderBackground(canvas, ctx); - } - - //reset in case of error - ctx.restore(); - ctx.setTransform(1, 0, 0, 1, 0, 0); - this.visible_links.length = 0; - - if (this.graph) { - //apply transformations - ctx.save(); - this.ds.toCanvasContext(ctx); - - //render BG - if ( - this.background_image && - this.ds.scale > 0.5 && - !bg_already_painted - ) { - if (this.zoom_modify_alpha) { - ctx.globalAlpha = - (1.0 - 0.5 / this.ds.scale) * this.editor_alpha; - } else { - ctx.globalAlpha = this.editor_alpha; - } - ctx.imageSmoothingEnabled = ctx.mozImageSmoothingEnabled = ctx.imageSmoothingEnabled = false; - if ( - !this._bg_img || - this._bg_img.name != this.background_image - ) { - this._bg_img = new Image(); - this._bg_img.name = this.background_image; - this._bg_img.src = this.background_image; - var that = this; - this._bg_img.onload = function() { - that.draw(true, true); - }; - } - - var pattern = null; - if (this._pattern == null && this._bg_img.width > 0) { - pattern = ctx.createPattern(this._bg_img, "repeat"); - this._pattern_img = this._bg_img; - this._pattern = pattern; - } else { - pattern = this._pattern; - } - if (pattern) { - ctx.fillStyle = pattern; - ctx.fillRect( - this.visible_area[0], - this.visible_area[1], - this.visible_area[2], - this.visible_area[3] - ); - ctx.fillStyle = "transparent"; - } - - ctx.globalAlpha = 1.0; - ctx.imageSmoothingEnabled = ctx.mozImageSmoothingEnabled = ctx.imageSmoothingEnabled = true; - } - - //groups - if (this.graph._groups.length && !this.live_mode) { - this.drawGroups(canvas, ctx); - } - - if (this.onDrawBackground) { - this.onDrawBackground(ctx, this.visible_area); - } - if (this.onBackgroundRender) { - //LEGACY - console.error( - "WARNING! onBackgroundRender deprecated, now is named onDrawBackground " - ); - this.onBackgroundRender = null; - } - - //DEBUG: show clipping area - //ctx.fillStyle = "red"; - //ctx.fillRect( this.visible_area[0] + 10, this.visible_area[1] + 10, this.visible_area[2] - 20, this.visible_area[3] - 20); - - //bg - if (this.render_canvas_border) { - ctx.strokeStyle = "#235"; - ctx.strokeRect(0, 0, canvas.width, canvas.height); - } - - if (this.render_connections_shadows) { - ctx.shadowColor = "#000"; - ctx.shadowOffsetX = 0; - ctx.shadowOffsetY = 0; - ctx.shadowBlur = 6; - } else { - ctx.shadowColor = "rgba(0,0,0,0)"; - } - - //draw connections - if (!this.live_mode) { - this.drawConnections(ctx); - } - - ctx.shadowColor = "rgba(0,0,0,0)"; - - //restore state - ctx.restore(); - } - - if (ctx.finish) { - ctx.finish(); - } - - this.dirty_bgcanvas = false; - this.dirty_canvas = true; //to force to repaint the front canvas with the bgcanvas - }; - - var temp_vec2 = new Float32Array(2); - - /** - * draws the given node inside the canvas - * @method drawNode - **/ - LGraphCanvas.prototype.drawNode = function(node, ctx) { - var glow = false; - this.current_node = node; - - var color = - node.color || - node.constructor.color || - LiteGraph.NODE_DEFAULT_COLOR; - var bgcolor = - node.bgcolor || - node.constructor.bgcolor || - LiteGraph.NODE_DEFAULT_BGCOLOR; - - //shadow and glow - if (node.mouseOver) { - glow = true; - } - - var low_quality = this.ds.scale < 0.6; //zoomed out - - //only render if it forces it to do it - if (this.live_mode) { - if (!node.flags.collapsed) { - ctx.shadowColor = "transparent"; - if (node.onDrawForeground) { - node.onDrawForeground(ctx, this, this.canvas); - } - } - return; - } - - var editor_alpha = this.editor_alpha; - ctx.globalAlpha = editor_alpha; - - if (this.render_shadows && !low_quality) { - ctx.shadowColor = LiteGraph.DEFAULT_SHADOW_COLOR; - ctx.shadowOffsetX = 2 * this.ds.scale; - ctx.shadowOffsetY = 2 * this.ds.scale; - ctx.shadowBlur = 3 * this.ds.scale; - } else { - ctx.shadowColor = "transparent"; - } - - //custom draw collapsed method (draw after shadows because they are affected) - if ( - node.flags.collapsed && - node.onDrawCollapsed && - node.onDrawCollapsed(ctx, this) == true - ) { - return; - } - - //clip if required (mask) - var shape = node._shape || LiteGraph.BOX_SHAPE; - var size = temp_vec2; - temp_vec2.set(node.size); - var horizontal = node.horizontal; // || node.flags.horizontal; - - if (node.flags.collapsed) { - ctx.font = this.inner_text_font; - var title = node.getTitle ? node.getTitle() : node.title; - if (title != null) { - node._collapsed_width = Math.min( - node.size[0], - ctx.measureText(title).width + - LiteGraph.NODE_TITLE_HEIGHT * 2 - ); //LiteGraph.NODE_COLLAPSED_WIDTH; - size[0] = node._collapsed_width; - size[1] = 0; - } - } - - if (node.clip_area) { - //Start clipping - ctx.save(); - ctx.beginPath(); - if (shape == LiteGraph.BOX_SHAPE) { - ctx.rect(0, 0, size[0], size[1]); - } else if (shape == LiteGraph.ROUND_SHAPE) { - ctx.roundRect(0, 0, size[0], size[1], 10); - } else if (shape == LiteGraph.CIRCLE_SHAPE) { - ctx.arc( - size[0] * 0.5, - size[1] * 0.5, - size[0] * 0.5, - 0, - Math.PI * 2 - ); - } - ctx.clip(); - } - - //draw shape - if (node.has_errors) { - bgcolor = "red"; - } - this.drawNodeShape( - node, - ctx, - size, - color, - bgcolor, - node.is_selected, - node.mouseOver - ); - ctx.shadowColor = "transparent"; - - //draw foreground - if (node.onDrawForeground) { - node.onDrawForeground(ctx, this, this.canvas); - } - - //connection slots - ctx.textAlign = horizontal ? "center" : "left"; - ctx.font = this.inner_text_font; - - var render_text = !low_quality; - - var out_slot = this.connecting_output; - ctx.lineWidth = 1; - - var max_y = 0; - var slot_pos = new Float32Array(2); //to reuse - - //render inputs and outputs - if (!node.flags.collapsed) { - //input connection slots - if (node.inputs) { - for (var i = 0; i < node.inputs.length; i++) { - var slot = node.inputs[i]; - - ctx.globalAlpha = editor_alpha; - //change opacity of incompatible slots when dragging a connection - if ( this.connecting_node && !LiteGraph.isValidConnection( slot.type , out_slot.type) ) { - ctx.globalAlpha = 0.4 * editor_alpha; - } - - ctx.fillStyle = - slot.link != null - ? slot.color_on || - this.default_connection_color.input_on - : slot.color_off || - this.default_connection_color.input_off; - - var pos = node.getConnectionPos(true, i, slot_pos); - pos[0] -= node.pos[0]; - pos[1] -= node.pos[1]; - if (max_y < pos[1] + LiteGraph.NODE_SLOT_HEIGHT * 0.5) { - max_y = pos[1] + LiteGraph.NODE_SLOT_HEIGHT * 0.5; - } - - ctx.beginPath(); - - if ( - slot.type === LiteGraph.EVENT || - slot.shape === LiteGraph.BOX_SHAPE - ) { - if (horizontal) { - ctx.rect( - pos[0] - 5 + 0.5, - pos[1] - 8 + 0.5, - 10, - 14 - ); - } else { - ctx.rect( - pos[0] - 6 + 0.5, - pos[1] - 5 + 0.5, - 14, - 10 - ); - } - } else if (slot.shape === LiteGraph.ARROW_SHAPE) { - ctx.moveTo(pos[0] + 8, pos[1] + 0.5); - ctx.lineTo(pos[0] - 4, pos[1] + 6 + 0.5); - ctx.lineTo(pos[0] - 4, pos[1] - 6 + 0.5); - ctx.closePath(); - } else { - if(low_quality) - ctx.rect(pos[0] - 4, pos[1] - 4, 8, 8 ); //faster - else - ctx.arc(pos[0], pos[1], 4, 0, Math.PI * 2); - } - ctx.fill(); - - //render name - if (render_text) { - var text = slot.label != null ? slot.label : slot.name; - if (text) { - ctx.fillStyle = LiteGraph.NODE_TEXT_COLOR; - if (horizontal || slot.dir == LiteGraph.UP) { - ctx.fillText(text, pos[0], pos[1] - 10); - } else { - ctx.fillText(text, pos[0] + 10, pos[1] + 5); - } - } - } - } - } - - //output connection slots - if (this.connecting_node) { - ctx.globalAlpha = 0.4 * editor_alpha; - } - - ctx.textAlign = horizontal ? "center" : "right"; - ctx.strokeStyle = "black"; - if (node.outputs) { - for (var i = 0; i < node.outputs.length; i++) { - var slot = node.outputs[i]; - - var pos = node.getConnectionPos(false, i, slot_pos); - pos[0] -= node.pos[0]; - pos[1] -= node.pos[1]; - if (max_y < pos[1] + LiteGraph.NODE_SLOT_HEIGHT * 0.5) { - max_y = pos[1] + LiteGraph.NODE_SLOT_HEIGHT * 0.5; - } - - ctx.fillStyle = - slot.links && slot.links.length - ? slot.color_on || - this.default_connection_color.output_on - : slot.color_off || - this.default_connection_color.output_off; - ctx.beginPath(); - //ctx.rect( node.size[0] - 14,i*14,10,10); - - if ( - slot.type === LiteGraph.EVENT || - slot.shape === LiteGraph.BOX_SHAPE - ) { - if (horizontal) { - ctx.rect( - pos[0] - 5 + 0.5, - pos[1] - 8 + 0.5, - 10, - 14 - ); - } else { - ctx.rect( - pos[0] - 6 + 0.5, - pos[1] - 5 + 0.5, - 14, - 10 - ); - } - } else if (slot.shape === LiteGraph.ARROW_SHAPE) { - ctx.moveTo(pos[0] + 8, pos[1] + 0.5); - ctx.lineTo(pos[0] - 4, pos[1] + 6 + 0.5); - ctx.lineTo(pos[0] - 4, pos[1] - 6 + 0.5); - ctx.closePath(); - } else { - if(low_quality) - ctx.rect(pos[0] - 4, pos[1] - 4, 8, 8 ); - else - ctx.arc(pos[0], pos[1], 4, 0, Math.PI * 2); - } - - //trigger - //if(slot.node_id != null && slot.slot == -1) - // ctx.fillStyle = "#F85"; - - //if(slot.links != null && slot.links.length) - ctx.fill(); - if(!low_quality) - ctx.stroke(); - - //render output name - if (render_text) { - var text = slot.label != null ? slot.label : slot.name; - if (text) { - ctx.fillStyle = LiteGraph.NODE_TEXT_COLOR; - if (horizontal || slot.dir == LiteGraph.DOWN) { - ctx.fillText(text, pos[0], pos[1] - 8); - } else { - ctx.fillText(text, pos[0] - 10, pos[1] + 5); - } - } - } - } - } - - ctx.textAlign = "left"; - ctx.globalAlpha = 1; - - if (node.widgets) { - var widgets_y = max_y; - if (horizontal || node.widgets_up) { - widgets_y = 2; - } - if( node.widgets_start_y != null ) - widgets_y = node.widgets_start_y; - this.drawNodeWidgets( - node, - widgets_y, - ctx, - this.node_widget && this.node_widget[0] == node - ? this.node_widget[1] - : null - ); - } - } else if (this.render_collapsed_slots) { - //if collapsed - var input_slot = null; - var output_slot = null; - - //get first connected slot to render - if (node.inputs) { - for (var i = 0; i < node.inputs.length; i++) { - var slot = node.inputs[i]; - if (slot.link == null) { - continue; - } - input_slot = slot; - break; - } - } - if (node.outputs) { - for (var i = 0; i < node.outputs.length; i++) { - var slot = node.outputs[i]; - if (!slot.links || !slot.links.length) { - continue; - } - output_slot = slot; - } - } - - if (input_slot) { - var x = 0; - var y = LiteGraph.NODE_TITLE_HEIGHT * -0.5; //center - if (horizontal) { - x = node._collapsed_width * 0.5; - y = -LiteGraph.NODE_TITLE_HEIGHT; - } - ctx.fillStyle = "#686"; - ctx.beginPath(); - if ( - slot.type === LiteGraph.EVENT || - slot.shape === LiteGraph.BOX_SHAPE - ) { - ctx.rect(x - 7 + 0.5, y - 4, 14, 8); - } else if (slot.shape === LiteGraph.ARROW_SHAPE) { - ctx.moveTo(x + 8, y); - ctx.lineTo(x + -4, y - 4); - ctx.lineTo(x + -4, y + 4); - ctx.closePath(); - } else { - ctx.arc(x, y, 4, 0, Math.PI * 2); - } - ctx.fill(); - } - - if (output_slot) { - var x = node._collapsed_width; - var y = LiteGraph.NODE_TITLE_HEIGHT * -0.5; //center - if (horizontal) { - x = node._collapsed_width * 0.5; - y = 0; - } - ctx.fillStyle = "#686"; - ctx.strokeStyle = "black"; - ctx.beginPath(); - if ( - slot.type === LiteGraph.EVENT || - slot.shape === LiteGraph.BOX_SHAPE - ) { - ctx.rect(x - 7 + 0.5, y - 4, 14, 8); - } else if (slot.shape === LiteGraph.ARROW_SHAPE) { - ctx.moveTo(x + 6, y); - ctx.lineTo(x - 6, y - 4); - ctx.lineTo(x - 6, y + 4); - ctx.closePath(); - } else { - ctx.arc(x, y, 4, 0, Math.PI * 2); - } - ctx.fill(); - //ctx.stroke(); - } - } - - if (node.clip_area) { - ctx.restore(); - } - - ctx.globalAlpha = 1.0; - }; - - //used by this.over_link_center - LGraphCanvas.prototype.drawLinkTooltip = function( ctx, link ) - { - var pos = link._pos; - ctx.fillStyle = "black"; - ctx.beginPath(); - ctx.arc( pos[0], pos[1], 3, 0, Math.PI * 2 ); - ctx.fill(); - - if(link.data == null) - return; - - if(this.onDrawLinkTooltip) - if( this.onDrawLinkTooltip(ctx,link,this) == true ) - return; - - var data = link.data; - var text = null; - - if( data.constructor === Number ) - text = data.toFixed(2); - else if( data.constructor === String ) - text = "\"" + data + "\""; - else if( data.constructor === Boolean ) - text = String(data); - else if (data.toToolTip) - text = data.toToolTip(); - else - text = "[" + data.constructor.name + "]"; - - if(text == null) - return; - - ctx.font = "14px Courier New"; - var info = ctx.measureText(text); - var w = info.width + 20; - var h = 24; - ctx.shadowColor = "black"; - ctx.shadowOffsetX = 2; - ctx.shadowOffsetY = 2; - ctx.shadowBlur = 3; - ctx.fillStyle = "#454"; - ctx.beginPath(); - ctx.roundRect( pos[0] - w*0.5, pos[1] - 15 - h, w, h,3, 3); - ctx.moveTo( pos[0] - 10, pos[1] - 15 ); - ctx.lineTo( pos[0] + 10, pos[1] - 15 ); - ctx.lineTo( pos[0], pos[1] - 5 ); - ctx.fill(); - ctx.shadowColor = "transparent"; - ctx.textAlign = "center"; - ctx.fillStyle = "#CEC"; - ctx.fillText(text, pos[0], pos[1] - 15 - h * 0.3); - } - - /** - * draws the shape of the given node in the canvas - * @method drawNodeShape - **/ - var tmp_area = new Float32Array(4); - - LGraphCanvas.prototype.drawNodeShape = function( - node, - ctx, - size, - fgcolor, - bgcolor, - selected, - mouse_over - ) { - //bg rect - ctx.strokeStyle = fgcolor; - ctx.fillStyle = bgcolor; - - var title_height = LiteGraph.NODE_TITLE_HEIGHT; - var low_quality = this.ds.scale < 0.5; - - //render node area depending on shape - var shape = - node._shape || node.constructor.shape || LiteGraph.ROUND_SHAPE; - - var title_mode = node.constructor.title_mode; - - var render_title = true; - if (title_mode == LiteGraph.TRANSPARENT_TITLE) { - render_title = false; - } else if (title_mode == LiteGraph.AUTOHIDE_TITLE && mouse_over) { - render_title = true; - } - - var area = tmp_area; - area[0] = 0; //x - area[1] = render_title ? -title_height : 0; //y - area[2] = size[0] + 1; //w - area[3] = render_title ? size[1] + title_height : size[1]; //h - - var old_alpha = ctx.globalAlpha; - - //full node shape - //if(node.flags.collapsed) - { - ctx.beginPath(); - if (shape == LiteGraph.BOX_SHAPE || low_quality) { - ctx.fillRect(area[0], area[1], area[2], area[3]); - } else if ( - shape == LiteGraph.ROUND_SHAPE || - shape == LiteGraph.CARD_SHAPE - ) { - ctx.roundRect( - area[0], - area[1], - area[2], - area[3], - this.round_radius, - shape == LiteGraph.CARD_SHAPE ? 0 : this.round_radius - ); - } else if (shape == LiteGraph.CIRCLE_SHAPE) { - ctx.arc( - size[0] * 0.5, - size[1] * 0.5, - size[0] * 0.5, - 0, - Math.PI * 2 - ); - } - ctx.fill(); - - //separator - if(!node.flags.collapsed) - { - ctx.shadowColor = "transparent"; - ctx.fillStyle = "rgba(0,0,0,0.2)"; - ctx.fillRect(0, -1, area[2], 2); - } - } - ctx.shadowColor = "transparent"; - - if (node.onDrawBackground) { - node.onDrawBackground(ctx, this, this.canvas); - } - - //title bg (remember, it is rendered ABOVE the node) - if (render_title || title_mode == LiteGraph.TRANSPARENT_TITLE) { - //title bar - if (node.onDrawTitleBar) { - node.onDrawTitleBar( - ctx, - title_height, - size, - this.ds.scale, - fgcolor - ); - } else if ( - title_mode != LiteGraph.TRANSPARENT_TITLE && - (node.constructor.title_color || this.render_title_colored) - ) { - var title_color = node.constructor.title_color || fgcolor; - - if (node.flags.collapsed) { - ctx.shadowColor = LiteGraph.DEFAULT_SHADOW_COLOR; - } - - //* gradient test - if (this.use_gradients) { - var grad = LGraphCanvas.gradients[title_color]; - if (!grad) { - grad = LGraphCanvas.gradients[ title_color ] = ctx.createLinearGradient(0, 0, 400, 0); - grad.addColorStop(0, title_color); - grad.addColorStop(1, "#000"); - } - ctx.fillStyle = grad; - } else { - ctx.fillStyle = title_color; - } - - //ctx.globalAlpha = 0.5 * old_alpha; - ctx.beginPath(); - if (shape == LiteGraph.BOX_SHAPE || low_quality) { - ctx.rect(0, -title_height, size[0] + 1, title_height); - } else if ( shape == LiteGraph.ROUND_SHAPE || shape == LiteGraph.CARD_SHAPE ) { - ctx.roundRect( - 0, - -title_height, - size[0] + 1, - title_height, - this.round_radius, - node.flags.collapsed ? this.round_radius : 0 - ); - } - ctx.fill(); - ctx.shadowColor = "transparent"; - } - - //title box - var box_size = 10; - if (node.onDrawTitleBox) { - node.onDrawTitleBox(ctx, title_height, size, this.ds.scale); - } else if ( - shape == LiteGraph.ROUND_SHAPE || - shape == LiteGraph.CIRCLE_SHAPE || - shape == LiteGraph.CARD_SHAPE - ) { - if (low_quality) { - ctx.fillStyle = "black"; - ctx.beginPath(); - ctx.arc( - title_height * 0.5, - title_height * -0.5, - box_size * 0.5 + 1, - 0, - Math.PI * 2 - ); - ctx.fill(); - } - - ctx.fillStyle = node.boxcolor || LiteGraph.NODE_DEFAULT_BOXCOLOR; - if(low_quality) - ctx.fillRect( title_height * 0.5 - box_size *0.5, title_height * -0.5 - box_size *0.5, box_size , box_size ); - else - { - ctx.beginPath(); - ctx.arc( - title_height * 0.5, - title_height * -0.5, - box_size * 0.5, - 0, - Math.PI * 2 - ); - ctx.fill(); - } - } else { - if (low_quality) { - ctx.fillStyle = "black"; - ctx.fillRect( - (title_height - box_size) * 0.5 - 1, - (title_height + box_size) * -0.5 - 1, - box_size + 2, - box_size + 2 - ); - } - ctx.fillStyle = node.boxcolor || LiteGraph.NODE_DEFAULT_BOXCOLOR; - ctx.fillRect( - (title_height - box_size) * 0.5, - (title_height + box_size) * -0.5, - box_size, - box_size - ); - } - ctx.globalAlpha = old_alpha; - - //title text - if (node.onDrawTitleText) { - node.onDrawTitleText( - ctx, - title_height, - size, - this.ds.scale, - this.title_text_font, - selected - ); - } - if (!low_quality) { - ctx.font = this.title_text_font; - var title = node.getTitle(); - if (title) { - if (selected) { - ctx.fillStyle = "white"; - } else { - ctx.fillStyle = - node.constructor.title_text_color || - this.node_title_color; - } - if (node.flags.collapsed) { - ctx.textAlign = "center"; - var measure = ctx.measureText(title); - ctx.fillText( - title, - title_height + measure.width * 0.5, - LiteGraph.NODE_TITLE_TEXT_Y - title_height - ); - ctx.textAlign = "left"; - } else { - ctx.textAlign = "left"; - ctx.fillText( - title, - title_height, - LiteGraph.NODE_TITLE_TEXT_Y - title_height - ); - } - } - } - - if (node.onDrawTitle) { - node.onDrawTitle(ctx); - } - } - - //render selection marker - if (selected) { - if (node.onBounding) { - node.onBounding(area); - } - - if (title_mode == LiteGraph.TRANSPARENT_TITLE) { - area[1] -= title_height; - area[3] += title_height; - } - ctx.lineWidth = 1; - ctx.globalAlpha = 0.8; - ctx.beginPath(); - if (shape == LiteGraph.BOX_SHAPE) { - ctx.rect( - -6 + area[0], - -6 + area[1], - 12 + area[2], - 12 + area[3] - ); - } else if ( - shape == LiteGraph.ROUND_SHAPE || - (shape == LiteGraph.CARD_SHAPE && node.flags.collapsed) - ) { - ctx.roundRect( - -6 + area[0], - -6 + area[1], - 12 + area[2], - 12 + area[3], - this.round_radius * 2 - ); - } else if (shape == LiteGraph.CARD_SHAPE) { - ctx.roundRect( - -6 + area[0], - -6 + area[1], - 12 + area[2], - 12 + area[3], - this.round_radius * 2, - 2 - ); - } else if (shape == LiteGraph.CIRCLE_SHAPE) { - ctx.arc( - size[0] * 0.5, - size[1] * 0.5, - size[0] * 0.5 + 6, - 0, - Math.PI * 2 - ); - } - ctx.strokeStyle = "#FFF"; - ctx.stroke(); - ctx.strokeStyle = fgcolor; - ctx.globalAlpha = 1; - } - }; - - var margin_area = new Float32Array(4); - var link_bounding = new Float32Array(4); - var tempA = new Float32Array(2); - var tempB = new Float32Array(2); - - /** - * draws every connection visible in the canvas - * OPTIMIZE THIS: pre-catch connections position instead of recomputing them every time - * @method drawConnections - **/ - LGraphCanvas.prototype.drawConnections = function(ctx) { - var now = LiteGraph.getTime(); - var visible_area = this.visible_area; - margin_area[0] = visible_area[0] - 20; - margin_area[1] = visible_area[1] - 20; - margin_area[2] = visible_area[2] + 40; - margin_area[3] = visible_area[3] + 40; - - //draw connections - ctx.lineWidth = this.connections_width; - - ctx.fillStyle = "#AAA"; - ctx.strokeStyle = "#AAA"; - ctx.globalAlpha = this.editor_alpha; - //for every node - var nodes = this.graph._nodes; - for (var n = 0, l = nodes.length; n < l; ++n) { - var node = nodes[n]; - //for every input (we render just inputs because it is easier as every slot can only have one input) - if (!node.inputs || !node.inputs.length) { - continue; - } - - for (var i = 0; i < node.inputs.length; ++i) { - var input = node.inputs[i]; - if (!input || input.link == null) { - continue; - } - var link_id = input.link; - var link = this.graph.links[link_id]; - if (!link) { - continue; - } - - //find link info - var start_node = this.graph.getNodeById(link.origin_id); - if (start_node == null) { - continue; - } - var start_node_slot = link.origin_slot; - var start_node_slotpos = null; - if (start_node_slot == -1) { - start_node_slotpos = [ - start_node.pos[0] + 10, - start_node.pos[1] + 10 - ]; - } else { - start_node_slotpos = start_node.getConnectionPos( - false, - start_node_slot, - tempA - ); - } - var end_node_slotpos = node.getConnectionPos(true, i, tempB); - - //compute link bounding - link_bounding[0] = start_node_slotpos[0]; - link_bounding[1] = start_node_slotpos[1]; - link_bounding[2] = end_node_slotpos[0] - start_node_slotpos[0]; - link_bounding[3] = end_node_slotpos[1] - start_node_slotpos[1]; - if (link_bounding[2] < 0) { - link_bounding[0] += link_bounding[2]; - link_bounding[2] = Math.abs(link_bounding[2]); - } - if (link_bounding[3] < 0) { - link_bounding[1] += link_bounding[3]; - link_bounding[3] = Math.abs(link_bounding[3]); - } - - //skip links outside of the visible area of the canvas - if (!overlapBounding(link_bounding, margin_area)) { - continue; - } - - var start_slot = start_node.outputs[start_node_slot]; - var end_slot = node.inputs[i]; - if (!start_slot || !end_slot) { - continue; - } - var start_dir = - start_slot.dir || - (start_node.horizontal ? LiteGraph.DOWN : LiteGraph.RIGHT); - var end_dir = - end_slot.dir || - (node.horizontal ? LiteGraph.UP : LiteGraph.LEFT); - - this.renderLink( - ctx, - start_node_slotpos, - end_node_slotpos, - link, - false, - 0, - null, - start_dir, - end_dir - ); - - //event triggered rendered on top - if (link && link._last_time && now - link._last_time < 1000) { - var f = 2.0 - (now - link._last_time) * 0.002; - var tmp = ctx.globalAlpha; - ctx.globalAlpha = tmp * f; - this.renderLink( - ctx, - start_node_slotpos, - end_node_slotpos, - link, - true, - f, - "white", - start_dir, - end_dir - ); - ctx.globalAlpha = tmp; - } - } - } - ctx.globalAlpha = 1; - }; - - /** - * draws a link between two points - * @method renderLink - * @param {vec2} a start pos - * @param {vec2} b end pos - * @param {Object} link the link object with all the link info - * @param {boolean} skip_border ignore the shadow of the link - * @param {boolean} flow show flow animation (for events) - * @param {string} color the color for the link - * @param {number} start_dir the direction enum - * @param {number} end_dir the direction enum - * @param {number} num_sublines number of sublines (useful to represent vec3 or rgb) - **/ - LGraphCanvas.prototype.renderLink = function( - ctx, - a, - b, - link, - skip_border, - flow, - color, - start_dir, - end_dir, - num_sublines - ) { - if (link) { - this.visible_links.push(link); - } - - //choose color - if (!color && link) { - color = link.color || LGraphCanvas.link_type_colors[link.type]; - } - if (!color) { - color = this.default_link_color; - } - if (link != null && this.highlighted_links[link.id]) { - color = "#FFF"; - } - - start_dir = start_dir || LiteGraph.RIGHT; - end_dir = end_dir || LiteGraph.LEFT; - - var dist = distance(a, b); - - if (this.render_connections_border && this.ds.scale > 0.6) { - ctx.lineWidth = this.connections_width + 4; - } - ctx.lineJoin = "round"; - num_sublines = num_sublines || 1; - if (num_sublines > 1) { - ctx.lineWidth = 0.5; - } - - //begin line shape - ctx.beginPath(); - for (var i = 0; i < num_sublines; i += 1) { - var offsety = (i - (num_sublines - 1) * 0.5) * 5; - - if (this.links_render_mode == LiteGraph.SPLINE_LINK) { - ctx.moveTo(a[0], a[1] + offsety); - var start_offset_x = 0; - var start_offset_y = 0; - var end_offset_x = 0; - var end_offset_y = 0; - switch (start_dir) { - case LiteGraph.LEFT: - start_offset_x = dist * -0.25; - break; - case LiteGraph.RIGHT: - start_offset_x = dist * 0.25; - break; - case LiteGraph.UP: - start_offset_y = dist * -0.25; - break; - case LiteGraph.DOWN: - start_offset_y = dist * 0.25; - break; - } - switch (end_dir) { - case LiteGraph.LEFT: - end_offset_x = dist * -0.25; - break; - case LiteGraph.RIGHT: - end_offset_x = dist * 0.25; - break; - case LiteGraph.UP: - end_offset_y = dist * -0.25; - break; - case LiteGraph.DOWN: - end_offset_y = dist * 0.25; - break; - } - ctx.bezierCurveTo( - a[0] + start_offset_x, - a[1] + start_offset_y + offsety, - b[0] + end_offset_x, - b[1] + end_offset_y + offsety, - b[0], - b[1] + offsety - ); - } else if (this.links_render_mode == LiteGraph.LINEAR_LINK) { - ctx.moveTo(a[0], a[1] + offsety); - var start_offset_x = 0; - var start_offset_y = 0; - var end_offset_x = 0; - var end_offset_y = 0; - switch (start_dir) { - case LiteGraph.LEFT: - start_offset_x = -1; - break; - case LiteGraph.RIGHT: - start_offset_x = 1; - break; - case LiteGraph.UP: - start_offset_y = -1; - break; - case LiteGraph.DOWN: - start_offset_y = 1; - break; - } - switch (end_dir) { - case LiteGraph.LEFT: - end_offset_x = -1; - break; - case LiteGraph.RIGHT: - end_offset_x = 1; - break; - case LiteGraph.UP: - end_offset_y = -1; - break; - case LiteGraph.DOWN: - end_offset_y = 1; - break; - } - var l = 15; - ctx.lineTo( - a[0] + start_offset_x * l, - a[1] + start_offset_y * l + offsety - ); - ctx.lineTo( - b[0] + end_offset_x * l, - b[1] + end_offset_y * l + offsety - ); - ctx.lineTo(b[0], b[1] + offsety); - } else if (this.links_render_mode == LiteGraph.STRAIGHT_LINK) { - ctx.moveTo(a[0], a[1]); - var start_x = a[0]; - var start_y = a[1]; - var end_x = b[0]; - var end_y = b[1]; - if (start_dir == LiteGraph.RIGHT) { - start_x += 10; - } else { - start_y += 10; - } - if (end_dir == LiteGraph.LEFT) { - end_x -= 10; - } else { - end_y -= 10; - } - ctx.lineTo(start_x, start_y); - ctx.lineTo((start_x + end_x) * 0.5, start_y); - ctx.lineTo((start_x + end_x) * 0.5, end_y); - ctx.lineTo(end_x, end_y); - ctx.lineTo(b[0], b[1]); - } else { - return; - } //unknown - } - - //rendering the outline of the connection can be a little bit slow - if ( - this.render_connections_border && - this.ds.scale > 0.6 && - !skip_border - ) { - ctx.strokeStyle = "rgba(0,0,0,0.5)"; - ctx.stroke(); - } - - ctx.lineWidth = this.connections_width; - ctx.fillStyle = ctx.strokeStyle = color; - ctx.stroke(); - //end line shape - - var pos = this.computeConnectionPoint(a, b, 0.5, start_dir, end_dir); - if (link && link._pos) { - link._pos[0] = pos[0]; - link._pos[1] = pos[1]; - } - - //render arrow in the middle - if ( - this.ds.scale >= 0.6 && - this.highquality_render && - end_dir != LiteGraph.CENTER - ) { - //render arrow - if (this.render_connection_arrows) { - //compute two points in the connection - var posA = this.computeConnectionPoint( - a, - b, - 0.25, - start_dir, - end_dir - ); - var posB = this.computeConnectionPoint( - a, - b, - 0.26, - start_dir, - end_dir - ); - var posC = this.computeConnectionPoint( - a, - b, - 0.75, - start_dir, - end_dir - ); - var posD = this.computeConnectionPoint( - a, - b, - 0.76, - start_dir, - end_dir - ); - - //compute the angle between them so the arrow points in the right direction - var angleA = 0; - var angleB = 0; - if (this.render_curved_connections) { - angleA = -Math.atan2(posB[0] - posA[0], posB[1] - posA[1]); - angleB = -Math.atan2(posD[0] - posC[0], posD[1] - posC[1]); - } else { - angleB = angleA = b[1] > a[1] ? 0 : Math.PI; - } - - //render arrow - ctx.save(); - ctx.translate(posA[0], posA[1]); - ctx.rotate(angleA); - ctx.beginPath(); - ctx.moveTo(-5, -3); - ctx.lineTo(0, +7); - ctx.lineTo(+5, -3); - ctx.fill(); - ctx.restore(); - ctx.save(); - ctx.translate(posC[0], posC[1]); - ctx.rotate(angleB); - ctx.beginPath(); - ctx.moveTo(-5, -3); - ctx.lineTo(0, +7); - ctx.lineTo(+5, -3); - ctx.fill(); - ctx.restore(); - } - - //circle - ctx.beginPath(); - ctx.arc(pos[0], pos[1], 5, 0, Math.PI * 2); - ctx.fill(); - } - - //render flowing points - if (flow) { - ctx.fillStyle = color; - for (var i = 0; i < 5; ++i) { - var f = (LiteGraph.getTime() * 0.001 + i * 0.2) % 1; - var pos = this.computeConnectionPoint( - a, - b, - f, - start_dir, - end_dir - ); - ctx.beginPath(); - ctx.arc(pos[0], pos[1], 5, 0, 2 * Math.PI); - ctx.fill(); - } - } - }; - - //returns the link center point based on curvature - LGraphCanvas.prototype.computeConnectionPoint = function( - a, - b, - t, - start_dir, - end_dir - ) { - start_dir = start_dir || LiteGraph.RIGHT; - end_dir = end_dir || LiteGraph.LEFT; - - var dist = distance(a, b); - var p0 = a; - var p1 = [a[0], a[1]]; - var p2 = [b[0], b[1]]; - var p3 = b; - - switch (start_dir) { - case LiteGraph.LEFT: - p1[0] += dist * -0.25; - break; - case LiteGraph.RIGHT: - p1[0] += dist * 0.25; - break; - case LiteGraph.UP: - p1[1] += dist * -0.25; - break; - case LiteGraph.DOWN: - p1[1] += dist * 0.25; - break; - } - switch (end_dir) { - case LiteGraph.LEFT: - p2[0] += dist * -0.25; - break; - case LiteGraph.RIGHT: - p2[0] += dist * 0.25; - break; - case LiteGraph.UP: - p2[1] += dist * -0.25; - break; - case LiteGraph.DOWN: - p2[1] += dist * 0.25; - break; - } - - var c1 = (1 - t) * (1 - t) * (1 - t); - var c2 = 3 * ((1 - t) * (1 - t)) * t; - var c3 = 3 * (1 - t) * (t * t); - var c4 = t * t * t; - - var x = c1 * p0[0] + c2 * p1[0] + c3 * p2[0] + c4 * p3[0]; - var y = c1 * p0[1] + c2 * p1[1] + c3 * p2[1] + c4 * p3[1]; - return [x, y]; - }; - - LGraphCanvas.prototype.drawExecutionOrder = function(ctx) { - ctx.shadowColor = "transparent"; - ctx.globalAlpha = 0.25; - - ctx.textAlign = "center"; - ctx.strokeStyle = "white"; - ctx.globalAlpha = 0.75; - - var visible_nodes = this.visible_nodes; - for (var i = 0; i < visible_nodes.length; ++i) { - var node = visible_nodes[i]; - ctx.fillStyle = "black"; - ctx.fillRect( - node.pos[0] - LiteGraph.NODE_TITLE_HEIGHT, - node.pos[1] - LiteGraph.NODE_TITLE_HEIGHT, - LiteGraph.NODE_TITLE_HEIGHT, - LiteGraph.NODE_TITLE_HEIGHT - ); - if (node.order == 0) { - ctx.strokeRect( - node.pos[0] - LiteGraph.NODE_TITLE_HEIGHT + 0.5, - node.pos[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5, - LiteGraph.NODE_TITLE_HEIGHT, - LiteGraph.NODE_TITLE_HEIGHT - ); - } - ctx.fillStyle = "#FFF"; - ctx.fillText( - node.order, - node.pos[0] + LiteGraph.NODE_TITLE_HEIGHT * -0.5, - node.pos[1] - 6 - ); - } - ctx.globalAlpha = 1; - }; - - /** - * draws the widgets stored inside a node - * @method drawNodeWidgets - **/ - LGraphCanvas.prototype.drawNodeWidgets = function( - node, - posY, - ctx, - active_widget - ) { - if (!node.widgets || !node.widgets.length) { - return 0; - } - var width = node.size[0]; - var widgets = node.widgets; - posY += 2; - var H = LiteGraph.NODE_WIDGET_HEIGHT; - var show_text = this.ds.scale > 0.5; - ctx.save(); - ctx.globalAlpha = this.editor_alpha; - var outline_color = LiteGraph.WIDGET_OUTLINE_COLOR; - var background_color = LiteGraph.WIDGET_BGCOLOR; - var text_color = LiteGraph.WIDGET_TEXT_COLOR; - var secondary_text_color = LiteGraph.WIDGET_SECONDARY_TEXT_COLOR; - var margin = 15; - - for (var i = 0; i < widgets.length; ++i) { - var w = widgets[i]; - var y = posY; - if (w.y) { - y = w.y; - } - w.last_y = y; - ctx.strokeStyle = outline_color; - ctx.fillStyle = "#222"; - ctx.textAlign = "left"; - if(w.disabled) - ctx.globalAlpha *= 0.5; - - switch (w.type) { - case "button": - if (w.clicked) { - ctx.fillStyle = "#AAA"; - w.clicked = false; - this.dirty_canvas = true; - } - ctx.fillRect(margin, y, width - margin * 2, H); - if(show_text) - ctx.strokeRect( margin, y, width - margin * 2, H ); - if (show_text) { - ctx.textAlign = "center"; - ctx.fillStyle = text_color; - ctx.fillText(w.name, width * 0.5, y + H * 0.7); - } - break; - case "toggle": - ctx.textAlign = "left"; - ctx.strokeStyle = outline_color; - ctx.fillStyle = background_color; - ctx.beginPath(); - if (show_text) - ctx.roundRect(margin, posY, width - margin * 2, H, H * 0.5); - else - ctx.rect(margin, posY, width - margin * 2, H ); - ctx.fill(); - if(show_text) - ctx.stroke(); - ctx.fillStyle = w.value ? "#89A" : "#333"; - ctx.beginPath(); - ctx.arc( width - margin * 2, y + H * 0.5, H * 0.36, 0, Math.PI * 2 ); - ctx.fill(); - if (show_text) { - ctx.fillStyle = secondary_text_color; - if (w.name != null) { - ctx.fillText(w.name, margin * 2, y + H * 0.7); - } - ctx.fillStyle = w.value ? text_color : secondary_text_color; - ctx.textAlign = "right"; - ctx.fillText( - w.value - ? w.options.on || "true" - : w.options.off || "false", - width - 40, - y + H * 0.7 - ); - } - break; - case "slider": - ctx.fillStyle = background_color; - ctx.fillRect(margin, y, width - margin * 2, H); - var range = w.options.max - w.options.min; - var nvalue = (w.value - w.options.min) / range; - ctx.fillStyle = active_widget == w ? "#89A" : "#678"; - ctx.fillRect(margin, y, nvalue * (width - margin * 2), H); - if(show_text) - ctx.strokeRect(margin, y, width - margin * 2, H); - if (w.marker) { - var marker_nvalue = (w.marker - w.options.min) / range; - ctx.fillStyle = "#AA9"; - ctx.fillRect( margin + marker_nvalue * (width - margin * 2), y, 2, H ); - } - if (show_text) { - ctx.textAlign = "center"; - ctx.fillStyle = text_color; - ctx.fillText( - w.name + " " + Number(w.value).toFixed(3), - width * 0.5, - y + H * 0.7 - ); - } - break; - case "number": - case "combo": - ctx.textAlign = "left"; - ctx.strokeStyle = outline_color; - ctx.fillStyle = background_color; - ctx.beginPath(); - if(show_text) - ctx.roundRect(margin, posY, width - margin * 2, H, H * 0.5); - else - ctx.rect(margin, posY, width - margin * 2, H ); - ctx.fill(); - if (show_text) { - ctx.stroke(); - ctx.fillStyle = text_color; - ctx.beginPath(); - ctx.moveTo(margin + 16, posY + 5); - ctx.lineTo(margin + 6, posY + H * 0.5); - ctx.lineTo(margin + 16, posY + H - 5); - ctx.fill(); - ctx.beginPath(); - ctx.moveTo(width - margin - 16, posY + 5); - ctx.lineTo(width - margin - 6, posY + H * 0.5); - ctx.lineTo(width - margin - 16, posY + H - 5); - ctx.fill(); - ctx.fillStyle = secondary_text_color; - ctx.fillText(w.name, margin * 2 + 5, y + H * 0.7); - ctx.fillStyle = text_color; - ctx.textAlign = "right"; - if (w.type == "number") { - ctx.fillText( - Number(w.value).toFixed( - w.options.precision !== undefined - ? w.options.precision - : 3 - ), - width - margin * 2 - 20, - y + H * 0.7 - ); - } else { - ctx.fillText( - w.value, - width - margin * 2 - 20, - y + H * 0.7 - ); - } - } - break; - case "string": - case "text": - ctx.textAlign = "left"; - ctx.strokeStyle = outline_color; - ctx.fillStyle = background_color; - ctx.beginPath(); - if (show_text) - ctx.roundRect(margin, posY, width - margin * 2, H, H * 0.5); - else - ctx.rect( margin, posY, width - margin * 2, H ); - ctx.fill(); - if (show_text) { - ctx.stroke(); - ctx.fillStyle = secondary_text_color; - if (w.name != null) { - ctx.fillText(w.name, margin * 2, y + H * 0.7); - } - ctx.fillStyle = text_color; - ctx.textAlign = "right"; - ctx.fillText(w.value, width - margin * 2, y + H * 0.7); - } - break; - default: - if (w.draw) { - w.draw(ctx, node, w, y, H); - } - break; - } - posY += H + 4; - ctx.globalAlpha = this.editor_alpha; - - } - ctx.restore(); - ctx.textAlign = "left"; - }; - - /** - * process an event on widgets - * @method processNodeWidgets - **/ - LGraphCanvas.prototype.processNodeWidgets = function( - node, - pos, - event, - active_widget - ) { - if (!node.widgets || !node.widgets.length) { - return null; - } - - var x = pos[0] - node.pos[0]; - var y = pos[1] - node.pos[1]; - var width = node.size[0]; - var that = this; - var ref_window = this.getCanvasWindow(); - - for (var i = 0; i < node.widgets.length; ++i) { - var w = node.widgets[i]; - if(w.disabled) - continue; - if ( w == active_widget || (x > 6 && x < width - 12 && y > w.last_y && y < w.last_y + LiteGraph.NODE_WIDGET_HEIGHT) ) { - //inside widget - switch (w.type) { - case "button": - if (event.type === "mousemove") { - break; - } - if (w.callback) { - setTimeout(function() { - w.callback(w, that, node, pos, event); - }, 20); - } - w.clicked = true; - this.dirty_canvas = true; - break; - case "slider": - var range = w.options.max - w.options.min; - var nvalue = Math.clamp((x - 10) / (width - 20), 0, 1); - w.value = - w.options.min + - (w.options.max - w.options.min) * nvalue; - if (w.callback) { - setTimeout(function() { - inner_value_change(w, w.value); - }, 20); - } - this.dirty_canvas = true; - break; - case "number": - case "combo": - var old_value = w.value; - if (event.type == "mousemove" && w.type == "number") { - w.value += - event.deltaX * 0.1 * (w.options.step || 1); - if ( - w.options.min != null && - w.value < w.options.min - ) { - w.value = w.options.min; - } - if ( - w.options.max != null && - w.value > w.options.max - ) { - w.value = w.options.max; - } - } else if (event.type == "mousedown") { - var values = w.options.values; - if (values && values.constructor === Function) { - values = w.options.values(w, node); - } - - var delta = x < 40 ? -1 : x > width - 40 ? 1 : 0; - if (w.type == "number") { - w.value += delta * 0.1 * (w.options.step || 1); - if ( - w.options.min != null && - w.value < w.options.min - ) { - w.value = w.options.min; - } - if ( - w.options.max != null && - w.value > w.options.max - ) { - w.value = w.options.max; - } - } else if (delta) { - var index = values.indexOf(w.value) + delta; - if (index >= values.length) { - index = 0; - } - if (index < 0) { - index = values.length - 1; - } - w.value = values[index]; - } else { - var menu = new LiteGraph.ContextMenu( - values, - { - scale: Math.max(1, this.ds.scale), - event: event, - className: "dark", - callback: inner_clicked.bind(w) - }, - ref_window - ); - function inner_clicked(v, option, event) { - this.value = v; - inner_value_change(this, v); - that.dirty_canvas = true; - return false; - } - } - } //mousedown - - if( old_value != w.value ) - setTimeout( - function() { - inner_value_change(this, this.value); - }.bind(w), - 20 - ); - this.dirty_canvas = true; - break; - case "toggle": - if (event.type == "mousedown") { - w.value = !w.value; - setTimeout(function() { - inner_value_change(w, w.value); - }, 20); - } - break; - case "string": - case "text": - if (event.type == "mousedown") { - this.prompt( - "Value", - w.value, - function(v) { - this.value = v; - inner_value_change(this, v); - }.bind(w), - event - ); - } - break; - default: - if (w.mouse) { - w.mouse(ctx, event, [x, y], node); - } - break; - } //end switch - - return w; - } - } - - function inner_value_change(widget, value) { - widget.value = value; - if ( widget.options && widget.options.property && node.properties[widget.options.property] !== undefined ) { - node.setProperty( widget.options.property, value ); - } - if (widget.callback) { - widget.callback(widget.value, that, node, pos, event); - } - } - - return null; - }; - - /** - * draws every group area in the background - * @method drawGroups - **/ - LGraphCanvas.prototype.drawGroups = function(canvas, ctx) { - if (!this.graph) { - return; - } - - var groups = this.graph._groups; - - ctx.save(); - ctx.globalAlpha = 0.5 * this.editor_alpha; - - for (var i = 0; i < groups.length; ++i) { - var group = groups[i]; - - if (!overlapBounding(this.visible_area, group._bounding)) { - continue; - } //out of the visible area - - ctx.fillStyle = group.color || "#335"; - ctx.strokeStyle = group.color || "#335"; - var pos = group._pos; - var size = group._size; - ctx.globalAlpha = 0.25 * this.editor_alpha; - ctx.beginPath(); - ctx.rect(pos[0] + 0.5, pos[1] + 0.5, size[0], size[1]); - ctx.fill(); - ctx.globalAlpha = this.editor_alpha; - ctx.stroke(); - - ctx.beginPath(); - ctx.moveTo(pos[0] + size[0], pos[1] + size[1]); - ctx.lineTo(pos[0] + size[0] - 10, pos[1] + size[1]); - ctx.lineTo(pos[0] + size[0], pos[1] + size[1] - 10); - ctx.fill(); - - var font_size = - group.font_size || LiteGraph.DEFAULT_GROUP_FONT_SIZE; - ctx.font = font_size + "px Arial"; - ctx.fillText(group.title, pos[0] + 4, pos[1] + font_size); - } - - ctx.restore(); - }; - - LGraphCanvas.prototype.adjustNodesSize = function() { - var nodes = this.graph._nodes; - for (var i = 0; i < nodes.length; ++i) { - nodes[i].size = nodes[i].computeSize(); - } - this.setDirty(true, true); - }; - - /** - * resizes the canvas to a given size, if no size is passed, then it tries to fill the parentNode - * @method resize - **/ - LGraphCanvas.prototype.resize = function(width, height) { - if (!width && !height) { - var parent = this.canvas.parentNode; - width = parent.offsetWidth; - height = parent.offsetHeight; - } - - if (this.canvas.width == width && this.canvas.height == height) { - return; - } - - this.canvas.width = width; - this.canvas.height = height; - this.bgcanvas.width = this.canvas.width; - this.bgcanvas.height = this.canvas.height; - this.setDirty(true, true); - }; - - /** - * switches to live mode (node shapes are not rendered, only the content) - * this feature was designed when graphs where meant to create user interfaces - * @method switchLiveMode - **/ - LGraphCanvas.prototype.switchLiveMode = function(transition) { - if (!transition) { - this.live_mode = !this.live_mode; - this.dirty_canvas = true; - this.dirty_bgcanvas = true; - return; - } - - var self = this; - var delta = this.live_mode ? 1.1 : 0.9; - if (this.live_mode) { - this.live_mode = false; - this.editor_alpha = 0.1; - } - - var t = setInterval(function() { - self.editor_alpha *= delta; - self.dirty_canvas = true; - self.dirty_bgcanvas = true; - - if (delta < 1 && self.editor_alpha < 0.01) { - clearInterval(t); - if (delta < 1) { - self.live_mode = true; - } - } - if (delta > 1 && self.editor_alpha > 0.99) { - clearInterval(t); - self.editor_alpha = 1; - } - }, 1); - }; - - LGraphCanvas.prototype.onNodeSelectionChange = function(node) { - return; //disabled - }; - - LGraphCanvas.prototype.touchHandler = function(event) { - //alert("foo"); - var touches = event.changedTouches, - first = touches[0], - type = ""; - - switch (event.type) { - case "touchstart": - type = "mousedown"; - break; - case "touchmove": - type = "mousemove"; - break; - case "touchend": - type = "mouseup"; - break; - default: - return; - } - - //initMouseEvent(type, canBubble, cancelable, view, clickCount, - // screenX, screenY, clientX, clientY, ctrlKey, - // altKey, shiftKey, metaKey, button, relatedTarget); - - var window = this.getCanvasWindow(); - var document = window.document; - - var simulatedEvent = document.createEvent("MouseEvent"); - simulatedEvent.initMouseEvent( - type, - true, - true, - window, - 1, - first.screenX, - first.screenY, - first.clientX, - first.clientY, - false, - false, - false, - false, - 0 /*left*/, - null - ); - first.target.dispatchEvent(simulatedEvent); - event.preventDefault(); - }; - - /* CONTEXT MENU ********************/ - - LGraphCanvas.onGroupAdd = function(info, entry, mouse_event) { - var canvas = LGraphCanvas.active_canvas; - var ref_window = canvas.getCanvasWindow(); - - var group = new LiteGraph.LGraphGroup(); - group.pos = canvas.convertEventToCanvasOffset(mouse_event); - canvas.graph.add(group); - }; - - LGraphCanvas.onMenuAdd = function(node, options, e, prev_menu, callback) { - var canvas = LGraphCanvas.active_canvas; - var ref_window = canvas.getCanvasWindow(); - - var values = LiteGraph.getNodeTypesCategories( canvas.filter ); - var entries = []; - for (var i=0; i < values.length; i++) { - if (values[i]) { - entries.push({ value: values[i], content: values[i], has_submenu: true }); - } - } - - //show categories - var menu = new LiteGraph.ContextMenu( entries, { event: e, callback: inner_clicked, parentMenu: prev_menu }, ref_window ); - - function inner_clicked(v, option, e) { - var category = v.value; - var node_types = LiteGraph.getNodeTypesInCategory( category, canvas.filter ); - var values = []; - for (var i=0; i < node_types.length; i++) { - if (!node_types[i].skip_list) { - values.push({ - content: node_types[i].title, - value: node_types[i].type - }); - } - } - - new LiteGraph.ContextMenu( values, { event: e, callback: inner_create, parentMenu: menu }, ref_window ); - return false; - } - - function inner_create(v, e) { - var first_event = prev_menu.getFirstEvent(); - var node = LiteGraph.createNode(v.value); - if (node) { - node.pos = canvas.convertEventToCanvasOffset(first_event); - canvas.graph.add(node); - } - if(callback) - callback(node); - } - - return false; - }; - - LGraphCanvas.onMenuCollapseAll = function() {}; - - LGraphCanvas.onMenuNodeEdit = function() {}; - - LGraphCanvas.showMenuNodeOptionalInputs = function( - v, - options, - e, - prev_menu, - node - ) { - if (!node) { - return; - } - - var that = this; - var canvas = LGraphCanvas.active_canvas; - var ref_window = canvas.getCanvasWindow(); - - var options = node.optional_inputs; - if (node.onGetInputs) { - options = node.onGetInputs(); - } - - var entries = []; - if (options) { - for (var i=0; i < options.length; i++) { - var entry = options[i]; - if (!entry) { - entries.push(null); - continue; - } - var label = entry[0]; - if (entry[2] && entry[2].label) { - label = entry[2].label; - } - var data = { content: label, value: entry }; - if (entry[1] == LiteGraph.ACTION) { - data.className = "event"; - } - entries.push(data); - } - } - - if (this.onMenuNodeInputs) { - entries = this.onMenuNodeInputs(entries); - } - - if (!entries.length) { - return; - } - - var menu = new LiteGraph.ContextMenu( - entries, - { - event: e, - callback: inner_clicked, - parentMenu: prev_menu, - node: node - }, - ref_window - ); - - function inner_clicked(v, e, prev) { - if (!node) { - return; - } - - if (v.callback) { - v.callback.call(that, node, v, e, prev); - } - - if (v.value) { - node.addInput(v.value[0], v.value[1], v.value[2]); - node.setDirtyCanvas(true, true); - } - } - - return false; - }; - - LGraphCanvas.showMenuNodeOptionalOutputs = function( - v, - options, - e, - prev_menu, - node - ) { - if (!node) { - return; - } - - var that = this; - var canvas = LGraphCanvas.active_canvas; - var ref_window = canvas.getCanvasWindow(); - - var options = node.optional_outputs; - if (node.onGetOutputs) { - options = node.onGetOutputs(); - } - - var entries = []; - if (options) { - for (var i=0; i < options.length; i++) { - var entry = options[i]; - if (!entry) { - //separator? - entries.push(null); - continue; - } - - if ( - node.flags && - node.flags.skip_repeated_outputs && - node.findOutputSlot(entry[0]) != -1 - ) { - continue; - } //skip the ones already on - var label = entry[0]; - if (entry[2] && entry[2].label) { - label = entry[2].label; - } - var data = { content: label, value: entry }; - if (entry[1] == LiteGraph.EVENT) { - data.className = "event"; - } - entries.push(data); - } - } - - if (this.onMenuNodeOutputs) { - entries = this.onMenuNodeOutputs(entries); - } - - if (!entries.length) { - return; - } - - var menu = new LiteGraph.ContextMenu( - entries, - { - event: e, - callback: inner_clicked, - parentMenu: prev_menu, - node: node - }, - ref_window - ); - - function inner_clicked(v, e, prev) { - if (!node) { - return; - } - - if (v.callback) { - v.callback.call(that, node, v, e, prev); - } - - if (!v.value) { - return; - } - - var value = v.value[1]; - - if ( - value && - (value.constructor === Object || value.constructor === Array) - ) { - //submenu why? - var entries = []; - for (var i in value) { - entries.push({ content: i, value: value[i] }); - } - new LiteGraph.ContextMenu(entries, { - event: e, - callback: inner_clicked, - parentMenu: prev_menu, - node: node - }); - return false; - } else { - node.addOutput(v.value[0], v.value[1], v.value[2]); - node.setDirtyCanvas(true, true); - } - } - - return false; - }; - - LGraphCanvas.onShowMenuNodeProperties = function( - value, - options, - e, - prev_menu, - node - ) { - if (!node || !node.properties) { - return; - } - - var that = this; - var canvas = LGraphCanvas.active_canvas; - var ref_window = canvas.getCanvasWindow(); - - var entries = []; - for (var i in node.properties) { - var value = node.properties[i] !== undefined ? node.properties[i] : " "; - if( typeof value == "object" ) - value = JSON.stringify(value); - //value could contain invalid html characters, clean that - value = LGraphCanvas.decodeHTML(value); - entries.push({ - content: - "" + - i + - "" + - "" + - value + - "", - value: i - }); - } - if (!entries.length) { - return; - } - - var menu = new LiteGraph.ContextMenu( - entries, - { - event: e, - callback: inner_clicked, - parentMenu: prev_menu, - allow_html: true, - node: node - }, - ref_window - ); - - function inner_clicked(v, options, e, prev) { - if (!node) { - return; - } - var rect = this.getBoundingClientRect(); - canvas.showEditPropertyValue(node, v.value, { - position: [rect.left, rect.top] - }); - } - - return false; - }; - - LGraphCanvas.decodeHTML = function(str) { - var e = document.createElement("div"); - e.innerText = str; - return e.innerHTML; - }; - - LGraphCanvas.onResizeNode = function(value, options, e, menu, node) { - if (!node) { - return; - } - node.size = node.computeSize(); - node.setDirtyCanvas(true, true); - }; - - LGraphCanvas.prototype.showLinkMenu = function(link, e) { - var that = this; - console.log(link); - var options = ["Add Node",null,"Delete"]; - var menu = new LiteGraph.ContextMenu(options, { - event: e, - title: link.data != null ? link.data.constructor.name : null, - callback: inner_clicked - }); - - function inner_clicked(v,options,e) { - switch (v) { - case "Add Node": - LGraphCanvas.onMenuAdd(null, null, e, menu, function(node){ - console.log("node autoconnect"); - var node_left = that.graph.getNodeById( link.origin_id ); - var node_right = that.graph.getNodeById( link.target_id ); - if(!node.inputs || !node.inputs.length || !node.outputs || !node.outputs.length) - return; - if( node_left.outputs[ link.origin_slot ].type == node.inputs[0].type && node.outputs[0].type == node_right.inputs[0].type ) - { - node_left.connect( link.origin_slot, node, 0 ); - node.connect( 0, node_right, link.target_slot ); - node.pos[0] -= node.size[0] * 0.5; - } - }); - break; - case "Delete": - that.graph.removeLink(link.id); - break; - default: - } - } - - return false; - }; - - LGraphCanvas.onShowPropertyEditor = function(item, options, e, menu, node) { - var input_html = ""; - var property = item.property || "title"; - var value = node[property]; - - var dialog = document.createElement("div"); - dialog.className = "graphdialog"; - dialog.innerHTML = - ""; - var title = dialog.querySelector(".name"); - title.innerText = property; - var input = dialog.querySelector("input"); - if (input) { - input.value = value; - input.addEventListener("blur", function(e) { - this.focus(); - }); - input.addEventListener("keydown", function(e) { - if (e.keyCode != 13) { - return; - } - inner(); - e.preventDefault(); - e.stopPropagation(); - }); - } - - var graphcanvas = LGraphCanvas.active_canvas; - var canvas = graphcanvas.canvas; - - var rect = canvas.getBoundingClientRect(); - var offsetx = -20; - var offsety = -20; - if (rect) { - offsetx -= rect.left; - offsety -= rect.top; - } - - if (event) { - dialog.style.left = event.clientX + offsetx + "px"; - dialog.style.top = event.clientY + offsety + "px"; - } else { - dialog.style.left = canvas.width * 0.5 + offsetx + "px"; - dialog.style.top = canvas.height * 0.5 + offsety + "px"; - } - - var button = dialog.querySelector("button"); - button.addEventListener("click", inner); - canvas.parentNode.appendChild(dialog); - - function inner() { - setValue(input.value); - } - - function setValue(value) { - if (item.type == "Number") { - value = Number(value); - } else if (item.type == "Boolean") { - value = Boolean(value); - } - node[property] = value; - if (dialog.parentNode) { - dialog.parentNode.removeChild(dialog); - } - node.setDirtyCanvas(true, true); - } - }; - - LGraphCanvas.prototype.prompt = function(title, value, callback, event) { - var that = this; - var input_html = ""; - title = title || ""; - - var modified = false; - - var dialog = document.createElement("div"); - dialog.className = "graphdialog rounded"; - dialog.innerHTML = - " "; - dialog.close = function() { - that.prompt_box = null; - if (dialog.parentNode) { - dialog.parentNode.removeChild(dialog); - } - }; - - if (this.ds.scale > 1) { - dialog.style.transform = "scale(" + this.ds.scale + ")"; - } - - dialog.addEventListener("mouseleave", function(e) { - if (!modified) { - dialog.close(); - } - }); - - if (that.prompt_box) { - that.prompt_box.close(); - } - that.prompt_box = dialog; - - var first = null; - var timeout = null; - var selected = null; - - var name_element = dialog.querySelector(".name"); - name_element.innerText = title; - var value_element = dialog.querySelector(".value"); - value_element.value = value; - - var input = dialog.querySelector("input"); - input.addEventListener("keydown", function(e) { - modified = true; - if (e.keyCode == 27) { - //ESC - dialog.close(); - } else if (e.keyCode == 13) { - if (callback) { - callback(this.value); - } - dialog.close(); - } else { - return; - } - e.preventDefault(); - e.stopPropagation(); - }); - - var button = dialog.querySelector("button"); - button.addEventListener("click", function(e) { - if (callback) { - callback(input.value); - } - that.setDirty(true); - dialog.close(); - }); - - var graphcanvas = LGraphCanvas.active_canvas; - var canvas = graphcanvas.canvas; - - var rect = canvas.getBoundingClientRect(); - var offsetx = -20; - var offsety = -20; - if (rect) { - offsetx -= rect.left; - offsety -= rect.top; - } - - if (event) { - dialog.style.left = event.clientX + offsetx + "px"; - dialog.style.top = event.clientY + offsety + "px"; - } else { - dialog.style.left = canvas.width * 0.5 + offsetx + "px"; - dialog.style.top = canvas.height * 0.5 + offsety + "px"; - } - - canvas.parentNode.appendChild(dialog); - setTimeout(function() { - input.focus(); - }, 10); - - return dialog; - }; - - LGraphCanvas.search_limit = -1; - LGraphCanvas.prototype.showSearchBox = function(event) { - var that = this; - var input_html = ""; - var graphcanvas = LGraphCanvas.active_canvas; - var canvas = graphcanvas.canvas; - var root_document = canvas.ownerDocument || document; - - var dialog = document.createElement("div"); - dialog.className = "litegraph litesearchbox graphdialog rounded"; - dialog.innerHTML = - "Search
"; - dialog.close = function() { - that.search_box = null; - root_document.body.focus(); - root_document.body.style.overflow = ""; - - setTimeout(function() { - that.canvas.focus(); - }, 20); //important, if canvas loses focus keys wont be captured - if (dialog.parentNode) { - dialog.parentNode.removeChild(dialog); - } - }; - - var timeout_close = null; - - if (this.ds.scale > 1) { - dialog.style.transform = "scale(" + this.ds.scale + ")"; - } - - dialog.addEventListener("mouseenter", function(e) { - if (timeout_close) { - clearTimeout(timeout_close); - timeout_close = null; - } - }); - - dialog.addEventListener("mouseleave", function(e) { - //dialog.close(); - timeout_close = setTimeout(function() { - dialog.close(); - }, 500); - }); - - if (that.search_box) { - that.search_box.close(); - } - that.search_box = dialog; - - var helper = dialog.querySelector(".helper"); - - var first = null; - var timeout = null; - var selected = null; - - var input = dialog.querySelector("input"); - if (input) { - input.addEventListener("blur", function(e) { - this.focus(); - }); - input.addEventListener("keydown", function(e) { - if (e.keyCode == 38) { - //UP - changeSelection(false); - } else if (e.keyCode == 40) { - //DOWN - changeSelection(true); - } else if (e.keyCode == 27) { - //ESC - dialog.close(); - } else if (e.keyCode == 13) { - if (selected) { - select(selected.innerHTML); - } else if (first) { - select(first); - } else { - dialog.close(); - } - } else { - if (timeout) { - clearInterval(timeout); - } - timeout = setTimeout(refreshHelper, 10); - return; - } - e.preventDefault(); - e.stopPropagation(); - e.stopImmediatePropagation(); - return true; - }); - } - - if( root_document.fullscreenElement ) - root_document.fullscreenElement.appendChild(dialog); - else - { - root_document.body.appendChild(dialog); - root_document.body.style.overflow = "hidden"; - } - - //compute best position - var rect = canvas.getBoundingClientRect(); - - var left = ( event ? event.clientX : (rect.left + rect.width * 0.5) ) - 80; - var top = ( event ? event.clientY : (rect.top + rect.height * 0.5) ) - 20; - dialog.style.left = left + "px"; - dialog.style.top = top + "px"; - - //To avoid out of screen problems - if(event.layerY > (rect.height - 200)) - helper.style.maxHeight = (rect.height - event.layerY - 20) + "px"; - - /* - var offsetx = -20; - var offsety = -20; - if (rect) { - offsetx -= rect.left; - offsety -= rect.top; - } - - if (event) { - dialog.style.left = event.clientX + offsetx + "px"; - dialog.style.top = event.clientY + offsety + "px"; - } else { - dialog.style.left = canvas.width * 0.5 + offsetx + "px"; - dialog.style.top = canvas.height * 0.5 + offsety + "px"; - } - canvas.parentNode.appendChild(dialog); - */ - - input.focus(); - - function select(name) { - if (name) { - if (that.onSearchBoxSelection) { - that.onSearchBoxSelection(name, event, graphcanvas); - } else { - var extra = LiteGraph.searchbox_extras[name.toLowerCase()]; - if (extra) { - name = extra.type; - } - - var node = LiteGraph.createNode(name); - if (node) { - node.pos = graphcanvas.convertEventToCanvasOffset( - event - ); - graphcanvas.graph.add(node); - } - - if (extra && extra.data) { - if (extra.data.properties) { - for (var i in extra.data.properties) { - node.addProperty( i, extra.data.properties[i] ); - } - } - if (extra.data.inputs) { - node.inputs = []; - for (var i in extra.data.inputs) { - node.addOutput( - extra.data.inputs[i][0], - extra.data.inputs[i][1] - ); - } - } - if (extra.data.outputs) { - node.outputs = []; - for (var i in extra.data.outputs) { - node.addOutput( - extra.data.outputs[i][0], - extra.data.outputs[i][1] - ); - } - } - if (extra.data.title) { - node.title = extra.data.title; - } - if (extra.data.json) { - node.configure(extra.data.json); - } - } - } - } - - dialog.close(); - } - - function changeSelection(forward) { - var prev = selected; - if (selected) { - selected.classList.remove("selected"); - } - if (!selected) { - selected = forward - ? helper.childNodes[0] - : helper.childNodes[helper.childNodes.length]; - } else { - selected = forward - ? selected.nextSibling - : selected.previousSibling; - if (!selected) { - selected = prev; - } - } - if (!selected) { - return; - } - selected.classList.add("selected"); - selected.scrollIntoView({block: "end", behavior: "smooth"}); - } - - function refreshHelper() { - timeout = null; - var str = input.value; - first = null; - helper.innerHTML = ""; - if (!str) { - return; - } - - if (that.onSearchBox) { - var list = that.onSearchBox(helper, str, graphcanvas); - if (list) { - for (var i = 0; i < list.length; ++i) { - addResult(list[i]); - } - } - } else { - var c = 0; - str = str.toLowerCase(); - var filter = graphcanvas.filter || graphcanvas.graph.filter; - - //extras - for (var i in LiteGraph.searchbox_extras) { - var extra = LiteGraph.searchbox_extras[i]; - if (extra.desc.toLowerCase().indexOf(str) === -1) { - continue; - } - var ctor = LiteGraph.registered_node_types[ extra.type ]; - if( ctor && ctor.filter && ctor.filter != filter ) - continue; - addResult( extra.desc, "searchbox_extra" ); - if ( LGraphCanvas.search_limit !== -1 && c++ > LGraphCanvas.search_limit ) { - break; - } - } - - var filtered = null; - if (Array.prototype.filter) { //filter supported - var keys = Object.keys( LiteGraph.registered_node_types ); //types - var filtered = keys.filter( inner_test_filter ); - } else { - filtered = []; - for (var i in LiteGraph.registered_node_types) { - if( inner_test_filter(i) ) - filtered.push(i); - } - } - - for (var i = 0; i < filtered.length; i++) { - addResult(filtered[i]); - if ( LGraphCanvas.search_limit !== -1 && c++ > LGraphCanvas.search_limit ) { - break; - } - } - - function inner_test_filter( type ) - { - var ctor = LiteGraph.registered_node_types[ type ]; - if(filter && ctor.filter != filter ) - return false; - return type.toLowerCase().indexOf(str) !== -1; - } - } - - function addResult(type, className) { - var help = document.createElement("div"); - if (!first) { - first = type; - } - help.innerText = type; - help.dataset["type"] = escape(type); - help.className = "litegraph lite-search-item"; - if (className) { - help.className += " " + className; - } - help.addEventListener("click", function(e) { - select(unescape(this.dataset["type"])); - }); - helper.appendChild(help); - } - } - - return dialog; - }; - - LGraphCanvas.prototype.showEditPropertyValue = function( - node, - property, - options - ) { - if (!node || node.properties[property] === undefined) { - return; - } - - options = options || {}; - var that = this; - - var type = "string"; - - if (node.properties[property] !== null) { - type = typeof node.properties[property]; - } - - var info = null; - if (node.getPropertyInfo) { - info = node.getPropertyInfo(property); - } - if (node.properties_info) { - for (var i = 0; i < node.properties_info.length; ++i) { - if (node.properties_info[i].name == property) { - info = node.properties_info[i]; - break; - } - } - } - - if (info !== undefined && info !== null && info.type) { - type = info.type; - } - - var input_html = ""; - - if (type == "string" || type == "number" || type == "array" || type == "object") { - input_html = ""; - } else if (type == "enum" && info.values) { - input_html = ""; - } else if (type == "boolean") { - input_html = - ""; - } else { - console.warn("unknown type: " + type); - return; - } - - var dialog = this.createDialog( - "" + - property + - "" + - input_html + - "", - options - ); - - if (type == "enum" && info.values) { - var input = dialog.querySelector("select"); - input.addEventListener("change", function(e) { - setValue(e.target.value); - //var index = e.target.value; - //setValue( e.options[e.selectedIndex].value ); - }); - } else if (type == "boolean") { - var input = dialog.querySelector("input"); - if (input) { - input.addEventListener("click", function(e) { - setValue(!!input.checked); - }); - } - } else { - var input = dialog.querySelector("input"); - if (input) { - input.addEventListener("blur", function(e) { - this.focus(); - }); - var v = node.properties[property] !== undefined ? node.properties[property] : ""; - v = JSON.stringify(v); - input.value = v; - input.addEventListener("keydown", function(e) { - if (e.keyCode != 13) { - return; - } - inner(); - e.preventDefault(); - e.stopPropagation(); - }); - } - } - - var button = dialog.querySelector("button"); - button.addEventListener("click", inner); - - function inner() { - setValue(input.value); - } - - function setValue(value) { - if (typeof node.properties[property] == "number") { - value = Number(value); - } - if (type == "array" || type == "object") { - value = JSON.parse(value); - } - node.properties[property] = value; - if (node._graph) { - node._graph._version++; - } - if (node.onPropertyChanged) { - node.onPropertyChanged(property, value); - } - dialog.close(); - node.setDirtyCanvas(true, true); - } - }; - - LGraphCanvas.prototype.createDialog = function(html, options) { - options = options || {}; - - var dialog = document.createElement("div"); - dialog.className = "graphdialog"; - dialog.innerHTML = html; - - var rect = this.canvas.getBoundingClientRect(); - var offsetx = -20; - var offsety = -20; - if (rect) { - offsetx -= rect.left; - offsety -= rect.top; - } - - if (options.position) { - offsetx += options.position[0]; - offsety += options.position[1]; - } else if (options.event) { - offsetx += options.event.clientX; - offsety += options.event.clientY; - } //centered - else { - offsetx += this.canvas.width * 0.5; - offsety += this.canvas.height * 0.5; - } - - dialog.style.left = offsetx + "px"; - dialog.style.top = offsety + "px"; - - this.canvas.parentNode.appendChild(dialog); - - dialog.close = function() { - if (this.parentNode) { - this.parentNode.removeChild(this); - } - }; - - return dialog; - }; - - LGraphCanvas.onMenuNodeCollapse = function(value, options, e, menu, node) { - node.collapse(); - }; - - LGraphCanvas.onMenuNodePin = function(value, options, e, menu, node) { - node.pin(); - }; - - LGraphCanvas.onMenuNodeMode = function(value, options, e, menu, node) { - new LiteGraph.ContextMenu( - ["Always", "On Event", "On Trigger", "Never"], - { event: e, callback: inner_clicked, parentMenu: menu, node: node } - ); - - function inner_clicked(v) { - if (!node) { - return; - } - switch (v) { - case "On Event": - node.mode = LiteGraph.ON_EVENT; - break; - case "On Trigger": - node.mode = LiteGraph.ON_TRIGGER; - break; - case "Never": - node.mode = LiteGraph.NEVER; - break; - case "Always": - default: - node.mode = LiteGraph.ALWAYS; - break; - } - } - - return false; - }; - - LGraphCanvas.onMenuNodeColors = function(value, options, e, menu, node) { - if (!node) { - throw "no node for color"; - } - - var values = []; - values.push({ - value: null, - content: - "No color" - }); - - for (var i in LGraphCanvas.node_colors) { - var color = LGraphCanvas.node_colors[i]; - var value = { - value: i, - content: - "" + - i + - "" - }; - values.push(value); - } - new LiteGraph.ContextMenu(values, { - event: e, - callback: inner_clicked, - parentMenu: menu, - node: node - }); - - function inner_clicked(v) { - if (!node) { - return; - } - - var color = v.value ? LGraphCanvas.node_colors[v.value] : null; - if (color) { - if (node.constructor === LiteGraph.LGraphGroup) { - node.color = color.groupcolor; - } else { - node.color = color.color; - node.bgcolor = color.bgcolor; - } - } else { - delete node.color; - delete node.bgcolor; - } - node.setDirtyCanvas(true, true); - } - - return false; - }; - - LGraphCanvas.onMenuNodeShapes = function(value, options, e, menu, node) { - if (!node) { - throw "no node passed"; - } - - new LiteGraph.ContextMenu(LiteGraph.VALID_SHAPES, { - event: e, - callback: inner_clicked, - parentMenu: menu, - node: node - }); - - function inner_clicked(v) { - if (!node) { - return; - } - node.shape = v; - node.setDirtyCanvas(true); - } - - return false; - }; - - LGraphCanvas.onMenuNodeRemove = function(value, options, e, menu, node) { - if (!node) { - throw "no node passed"; - } - - if (node.removable === false) { - return; - } - - node.graph.remove(node); - node.setDirtyCanvas(true, true); - }; - - LGraphCanvas.onMenuNodeClone = function(value, options, e, menu, node) { - if (node.clonable == false) { - return; - } - var newnode = node.clone(); - if (!newnode) { - return; - } - newnode.pos = [node.pos[0] + 5, node.pos[1] + 5]; - node.graph.add(newnode); - node.setDirtyCanvas(true, true); - }; - - LGraphCanvas.node_colors = { - red: { color: "#322", bgcolor: "#533", groupcolor: "#A88" }, - brown: { color: "#332922", bgcolor: "#593930", groupcolor: "#b06634" }, - green: { color: "#232", bgcolor: "#353", groupcolor: "#8A8" }, - blue: { color: "#223", bgcolor: "#335", groupcolor: "#88A" }, - pale_blue: { - color: "#2a363b", - bgcolor: "#3f5159", - groupcolor: "#3f789e" - }, - cyan: { color: "#233", bgcolor: "#355", groupcolor: "#8AA" }, - purple: { color: "#323", bgcolor: "#535", groupcolor: "#a1309b" }, - yellow: { color: "#432", bgcolor: "#653", groupcolor: "#b58b2a" }, - black: { color: "#222", bgcolor: "#000", groupcolor: "#444" } - }; - - LGraphCanvas.prototype.getCanvasMenuOptions = function() { - var options = null; - if (this.getMenuOptions) { - options = this.getMenuOptions(); - } else { - options = [ - { - content: "Add Node", - has_submenu: true, - callback: LGraphCanvas.onMenuAdd - }, - { content: "Add Group", callback: LGraphCanvas.onGroupAdd } - //{content:"Collapse All", callback: LGraphCanvas.onMenuCollapseAll } - ]; - - if (this._graph_stack && this._graph_stack.length > 0) { - options.push(null, { - content: "Close subgraph", - callback: this.closeSubgraph.bind(this) - }); - } - } - - if (this.getExtraMenuOptions) { - var extra = this.getExtraMenuOptions(this, options); - if (extra) { - options = options.concat(extra); - } - } - - return options; - }; - - //called by processContextMenu to extract the menu list - LGraphCanvas.prototype.getNodeMenuOptions = function(node) { - var options = null; - - if (node.getMenuOptions) { - options = node.getMenuOptions(this); - } else { - options = [ - { - content: "Inputs", - has_submenu: true, - disabled: true, - callback: LGraphCanvas.showMenuNodeOptionalInputs - }, - { - content: "Outputs", - has_submenu: true, - disabled: true, - callback: LGraphCanvas.showMenuNodeOptionalOutputs - }, - null, - { - content: "Properties", - has_submenu: true, - callback: LGraphCanvas.onShowMenuNodeProperties - }, - null, - { - content: "Title", - callback: LGraphCanvas.onShowPropertyEditor - }, - { - content: "Mode", - has_submenu: true, - callback: LGraphCanvas.onMenuNodeMode - }, - { content: "Resize", callback: LGraphCanvas.onResizeNode }, - { - content: "Collapse", - callback: LGraphCanvas.onMenuNodeCollapse - }, - { content: "Pin", callback: LGraphCanvas.onMenuNodePin }, - { - content: "Colors", - has_submenu: true, - callback: LGraphCanvas.onMenuNodeColors - }, - { - content: "Shapes", - has_submenu: true, - callback: LGraphCanvas.onMenuNodeShapes - }, - null - ]; - } - - if (node.onGetInputs) { - var inputs = node.onGetInputs(); - if (inputs && inputs.length) { - options[0].disabled = false; - } - } - - if (node.onGetOutputs) { - var outputs = node.onGetOutputs(); - if (outputs && outputs.length) { - options[1].disabled = false; - } - } - - if (node.getExtraMenuOptions) { - var extra = node.getExtraMenuOptions(this); - if (extra) { - extra.push(null); - options = extra.concat(options); - } - } - - if (node.clonable !== false) { - options.push({ - content: "Clone", - callback: LGraphCanvas.onMenuNodeClone - }); - } - if (node.removable !== false) { - options.push(null, { - content: "Remove", - callback: LGraphCanvas.onMenuNodeRemove - }); - } - - if (node.graph && node.graph.onGetNodeMenuOptions) { - node.graph.onGetNodeMenuOptions(options, node); - } - - return options; - }; - - LGraphCanvas.prototype.getGroupMenuOptions = function(node) { - var o = [ - { content: "Title", callback: LGraphCanvas.onShowPropertyEditor }, - { - content: "Color", - has_submenu: true, - callback: LGraphCanvas.onMenuNodeColors - }, - { - content: "Font size", - property: "font_size", - type: "Number", - callback: LGraphCanvas.onShowPropertyEditor - }, - null, - { content: "Remove", callback: LGraphCanvas.onMenuNodeRemove } - ]; - - return o; - }; - - LGraphCanvas.prototype.processContextMenu = function(node, event) { - var that = this; - var canvas = LGraphCanvas.active_canvas; - var ref_window = canvas.getCanvasWindow(); - - var menu_info = null; - var options = { - event: event, - callback: inner_option_clicked, - extra: node - }; - - if(node) - options.title = node.type; - - //check if mouse is in input - var slot = null; - if (node) { - slot = node.getSlotInPosition(event.canvasX, event.canvasY); - LGraphCanvas.active_node = node; - } - - if (slot) { - //on slot - menu_info = []; - if ( - slot && - slot.output && - slot.output.links && - slot.output.links.length - ) { - menu_info.push({ content: "Disconnect Links", slot: slot }); - } - var _slot = slot.input || slot.output; - menu_info.push( - _slot.locked - ? "Cannot remove" - : { content: "Remove Slot", slot: slot } - ); - menu_info.push( - _slot.nameLocked - ? "Cannot rename" - : { content: "Rename Slot", slot: slot } - ); - options.title = - (slot.input ? slot.input.type : slot.output.type) || "*"; - if (slot.input && slot.input.type == LiteGraph.ACTION) { - options.title = "Action"; - } - if (slot.output && slot.output.type == LiteGraph.EVENT) { - options.title = "Event"; - } - } else { - if (node) { - //on node - menu_info = this.getNodeMenuOptions(node); - } else { - menu_info = this.getCanvasMenuOptions(); - var group = this.graph.getGroupOnPos( - event.canvasX, - event.canvasY - ); - if (group) { - //on group - menu_info.push(null, { - content: "Edit Group", - has_submenu: true, - submenu: { - title: "Group", - extra: group, - options: this.getGroupMenuOptions(group) - } - }); - } - } - } - - //show menu - if (!menu_info) { - return; - } - - var menu = new LiteGraph.ContextMenu(menu_info, options, ref_window); - - function inner_option_clicked(v, options, e) { - if (!v) { - return; - } - - if (v.content == "Remove Slot") { - var info = v.slot; - if (info.input) { - node.removeInput(info.slot); - } else if (info.output) { - node.removeOutput(info.slot); - } - return; - } else if (v.content == "Disconnect Links") { - var info = v.slot; - if (info.output) { - node.disconnectOutput(info.slot); - } else if (info.input) { - node.disconnectInput(info.slot); - } - return; - } else if (v.content == "Rename Slot") { - var info = v.slot; - var slot_info = info.input - ? node.getInputInfo(info.slot) - : node.getOutputInfo(info.slot); - var dialog = that.createDialog( - "Name", - options - ); - var input = dialog.querySelector("input"); - if (input && slot_info) { - input.value = slot_info.label || ""; - } - dialog - .querySelector("button") - .addEventListener("click", function(e) { - if (input.value) { - if (slot_info) { - slot_info.label = input.value; - } - that.setDirty(true); - } - dialog.close(); - }); - } - - //if(v.callback) - // return v.callback.call(that, node, options, e, menu, that, event ); - } - }; - - //API ************************************************* - //like rect but rounded corners - if (this.CanvasRenderingContext2D) { - CanvasRenderingContext2D.prototype.roundRect = function( - x, - y, - width, - height, - radius, - radius_low - ) { - if (radius === undefined) { - radius = 5; - } - - if (radius_low === undefined) { - radius_low = radius; - } - - this.moveTo(x + radius, y); - this.lineTo(x + width - radius, y); - this.quadraticCurveTo(x + width, y, x + width, y + radius); - - this.lineTo(x + width, y + height - radius_low); - this.quadraticCurveTo( - x + width, - y + height, - x + width - radius_low, - y + height - ); - this.lineTo(x + radius_low, y + height); - this.quadraticCurveTo(x, y + height, x, y + height - radius_low); - this.lineTo(x, y + radius); - this.quadraticCurveTo(x, y, x + radius, y); - }; - } - - function compareObjects(a, b) { - for (var i in a) { - if (a[i] != b[i]) { - return false; - } - } - return true; - } - LiteGraph.compareObjects = compareObjects; - - function distance(a, b) { - return Math.sqrt( - (b[0] - a[0]) * (b[0] - a[0]) + (b[1] - a[1]) * (b[1] - a[1]) - ); - } - LiteGraph.distance = distance; - - function colorToString(c) { - return ( - "rgba(" + - Math.round(c[0] * 255).toFixed() + - "," + - Math.round(c[1] * 255).toFixed() + - "," + - Math.round(c[2] * 255).toFixed() + - "," + - (c.length == 4 ? c[3].toFixed(2) : "1.0") + - ")" - ); - } - LiteGraph.colorToString = colorToString; - - function isInsideRectangle(x, y, left, top, width, height) { - if (left < x && left + width > x && top < y && top + height > y) { - return true; - } - return false; - } - LiteGraph.isInsideRectangle = isInsideRectangle; - - //[minx,miny,maxx,maxy] - function growBounding(bounding, x, y) { - if (x < bounding[0]) { - bounding[0] = x; - } else if (x > bounding[2]) { - bounding[2] = x; - } - - if (y < bounding[1]) { - bounding[1] = y; - } else if (y > bounding[3]) { - bounding[3] = y; - } - } - LiteGraph.growBounding = growBounding; - - //point inside bounding box - function isInsideBounding(p, bb) { - if ( - p[0] < bb[0][0] || - p[1] < bb[0][1] || - p[0] > bb[1][0] || - p[1] > bb[1][1] - ) { - return false; - } - return true; - } - LiteGraph.isInsideBounding = isInsideBounding; - - //bounding overlap, format: [ startx, starty, width, height ] - function overlapBounding(a, b) { - var A_end_x = a[0] + a[2]; - var A_end_y = a[1] + a[3]; - var B_end_x = b[0] + b[2]; - var B_end_y = b[1] + b[3]; - - if ( - a[0] > B_end_x || - a[1] > B_end_y || - A_end_x < b[0] || - A_end_y < b[1] - ) { - return false; - } - return true; - } - LiteGraph.overlapBounding = overlapBounding; - - //Convert a hex value to its decimal value - the inputted hex must be in the - // format of a hex triplet - the kind we use for HTML colours. The function - // will return an array with three values. - function hex2num(hex) { - if (hex.charAt(0) == "#") { - hex = hex.slice(1); - } //Remove the '#' char - if there is one. - hex = hex.toUpperCase(); - var hex_alphabets = "0123456789ABCDEF"; - var value = new Array(3); - var k = 0; - var int1, int2; - for (var i = 0; i < 6; i += 2) { - int1 = hex_alphabets.indexOf(hex.charAt(i)); - int2 = hex_alphabets.indexOf(hex.charAt(i + 1)); - value[k] = int1 * 16 + int2; - k++; - } - return value; - } - - LiteGraph.hex2num = hex2num; - - //Give a array with three values as the argument and the function will return - // the corresponding hex triplet. - function num2hex(triplet) { - var hex_alphabets = "0123456789ABCDEF"; - var hex = "#"; - var int1, int2; - for (var i = 0; i < 3; i++) { - int1 = triplet[i] / 16; - int2 = triplet[i] % 16; - - hex += hex_alphabets.charAt(int1) + hex_alphabets.charAt(int2); - } - return hex; - } - - LiteGraph.num2hex = num2hex; - - /* LiteGraph GUI elements used for canvas editing *************************************/ - - /** - * ContextMenu from LiteGUI - * - * @class ContextMenu - * @constructor - * @param {Array} values (allows object { title: "Nice text", callback: function ... }) - * @param {Object} options [optional] Some options:\ - * - title: title to show on top of the menu - * - callback: function to call when an option is clicked, it receives the item information - * - ignore_item_callbacks: ignores the callback inside the item, it just calls the options.callback - * - event: you can pass a MouseEvent, this way the ContextMenu appears in that position - */ - function ContextMenu(values, options) { - options = options || {}; - this.options = options; - var that = this; - - //to link a menu with its parent - if (options.parentMenu) { - if (options.parentMenu.constructor !== this.constructor) { - console.error( - "parentMenu must be of class ContextMenu, ignoring it" - ); - options.parentMenu = null; - } else { - this.parentMenu = options.parentMenu; - this.parentMenu.lock = true; - this.parentMenu.current_submenu = this; - } - } - - if ( - options.event && - options.event.constructor !== MouseEvent && - options.event.constructor !== CustomEvent && - options.event.constructor !== PointerEvent - ) { - console.error( - "Event passed to ContextMenu is not of type MouseEvent or CustomEvent. Ignoring it." - ); - options.event = null; - } - - var root = document.createElement("div"); - root.className = "litegraph litecontextmenu litemenubar-panel"; - if (options.className) { - root.className += " " + options.className; - } - root.style.minWidth = 100; - root.style.minHeight = 100; - root.style.pointerEvents = "none"; - setTimeout(function() { - root.style.pointerEvents = "auto"; - }, 100); //delay so the mouse up event is not caught by this element - - //this prevents the default context browser menu to open in case this menu was created when pressing right button - root.addEventListener( - "mouseup", - function(e) { - e.preventDefault(); - return true; - }, - true - ); - root.addEventListener( - "contextmenu", - function(e) { - if (e.button != 2) { - //right button - return false; - } - e.preventDefault(); - return false; - }, - true - ); - - root.addEventListener( - "mousedown", - function(e) { - if (e.button == 2) { - that.close(); - e.preventDefault(); - return true; - } - }, - true - ); - - function on_mouse_wheel(e) { - var pos = parseInt(root.style.top); - root.style.top = - (pos + e.deltaY * options.scroll_speed).toFixed() + "px"; - e.preventDefault(); - return true; - } - - if (!options.scroll_speed) { - options.scroll_speed = 0.1; - } - - root.addEventListener("wheel", on_mouse_wheel, true); - root.addEventListener("mousewheel", on_mouse_wheel, true); - - this.root = root; - - //title - if (options.title) { - var element = document.createElement("div"); - element.className = "litemenu-title"; - element.innerHTML = options.title; - root.appendChild(element); - } - - //entries - var num = 0; - for (var i=0; i < values.length; i++) { - var name = values.constructor == Array ? values[i] : i; - if (name != null && name.constructor !== String) { - name = name.content === undefined ? String(name) : name.content; - } - var value = values[i]; - this.addItem(name, value, options); - num++; - } - - //close on leave - root.addEventListener("mouseleave", function(e) { - if (that.lock) { - return; - } - if (root.closing_timer) { - clearTimeout(root.closing_timer); - } - root.closing_timer = setTimeout(that.close.bind(that, e), 500); - //that.close(e); - }); - - root.addEventListener("mouseenter", function(e) { - if (root.closing_timer) { - clearTimeout(root.closing_timer); - } - }); - - //insert before checking position - var root_document = document; - if (options.event) { - root_document = options.event.target.ownerDocument; - } - - if (!root_document) { - root_document = document; - } - - if( root_document.fullscreenElement ) - root_document.fullscreenElement.appendChild(root); - else - root_document.body.appendChild(root); - - //compute best position - var left = options.left || 0; - var top = options.top || 0; - if (options.event) { - left = options.event.clientX - 10; - top = options.event.clientY - 10; - if (options.title) { - top -= 20; - } - - if (options.parentMenu) { - var rect = options.parentMenu.root.getBoundingClientRect(); - left = rect.left + rect.width; - } - - var body_rect = document.body.getBoundingClientRect(); - var root_rect = root.getBoundingClientRect(); - - if (left > body_rect.width - root_rect.width - 10) { - left = body_rect.width - root_rect.width - 10; - } - if (top > body_rect.height - root_rect.height - 10) { - top = body_rect.height - root_rect.height - 10; - } - } - - root.style.left = left + "px"; - root.style.top = top + "px"; - - if (options.scale) { - root.style.transform = "scale(" + options.scale + ")"; - } - } - - ContextMenu.prototype.addItem = function(name, value, options) { - var that = this; - options = options || {}; - - var element = document.createElement("div"); - element.className = "litemenu-entry submenu"; - - var disabled = false; - - if (value === null) { - element.classList.add("separator"); - //element.innerHTML = "
" - //continue; - } else { - element.innerHTML = value && value.title ? value.title : name; - element.value = value; - - if (value) { - if (value.disabled) { - disabled = true; - element.classList.add("disabled"); - } - if (value.submenu || value.has_submenu) { - element.classList.add("has_submenu"); - } - } - - if (typeof value == "function") { - element.dataset["value"] = name; - element.onclick_callback = value; - } else { - element.dataset["value"] = value; - } - - if (value.className) { - element.className += " " + value.className; - } - } - - this.root.appendChild(element); - if (!disabled) { - element.addEventListener("click", inner_onclick); - } - if (options.autoopen) { - element.addEventListener("mouseenter", inner_over); - } - - function inner_over(e) { - var value = this.value; - if (!value || !value.has_submenu) { - return; - } - //if it is a submenu, autoopen like the item was clicked - inner_onclick.call(this, e); - } - - //menu option clicked - function inner_onclick(e) { - var value = this.value; - var close_parent = true; - - if (that.current_submenu) { - that.current_submenu.close(e); - } - - //global callback - if (options.callback) { - var r = options.callback.call( - this, - value, - options, - e, - that, - options.node - ); - if (r === true) { - close_parent = false; - } - } - - //special cases - if (value) { - if ( - value.callback && - !options.ignore_item_callbacks && - value.disabled !== true - ) { - //item callback - var r = value.callback.call( - this, - value, - options, - e, - that, - options.extra - ); - if (r === true) { - close_parent = false; - } - } - if (value.submenu) { - if (!value.submenu.options) { - throw "ContextMenu submenu needs options"; - } - var submenu = new that.constructor(value.submenu.options, { - callback: value.submenu.callback, - event: e, - parentMenu: that, - ignore_item_callbacks: - value.submenu.ignore_item_callbacks, - title: value.submenu.title, - extra: value.submenu.extra, - autoopen: options.autoopen - }); - close_parent = false; - } - } - - if (close_parent && !that.lock) { - that.close(); - } - } - - return element; - }; - - ContextMenu.prototype.close = function(e, ignore_parent_menu) { - if (this.root.parentNode) { - this.root.parentNode.removeChild(this.root); - } - if (this.parentMenu && !ignore_parent_menu) { - this.parentMenu.lock = false; - this.parentMenu.current_submenu = null; - if (e === undefined) { - this.parentMenu.close(); - } else if ( - e && - !ContextMenu.isCursorOverElement(e, this.parentMenu.root) - ) { - ContextMenu.trigger(this.parentMenu.root, "mouseleave", e); - } - } - if (this.current_submenu) { - this.current_submenu.close(e, true); - } - - if (this.root.closing_timer) { - clearTimeout(this.root.closing_timer); - } - }; - - //this code is used to trigger events easily (used in the context menu mouseleave - ContextMenu.trigger = function(element, event_name, params, origin) { - var evt = document.createEvent("CustomEvent"); - evt.initCustomEvent(event_name, true, true, params); //canBubble, cancelable, detail - evt.srcElement = origin; - if (element.dispatchEvent) { - element.dispatchEvent(evt); - } else if (element.__events) { - element.__events.dispatchEvent(evt); - } - //else nothing seems binded here so nothing to do - return evt; - }; - - //returns the top most menu - ContextMenu.prototype.getTopMenu = function() { - if (this.options.parentMenu) { - return this.options.parentMenu.getTopMenu(); - } - return this; - }; - - ContextMenu.prototype.getFirstEvent = function() { - if (this.options.parentMenu) { - return this.options.parentMenu.getFirstEvent(); - } - return this.options.event; - }; - - ContextMenu.isCursorOverElement = function(event, element) { - var left = event.clientX; - var top = event.clientY; - var rect = element.getBoundingClientRect(); - if (!rect) { - return false; - } - if ( - top > rect.top && - top < rect.top + rect.height && - left > rect.left && - left < rect.left + rect.width - ) { - return true; - } - return false; - }; - - LiteGraph.ContextMenu = ContextMenu; - - LiteGraph.closeAllContextMenus = function(ref_window) { - ref_window = ref_window || window; - - var elements = ref_window.document.querySelectorAll(".litecontextmenu"); - if (!elements.length) { - return; - } - - var result = []; - for (var i = 0; i < elements.length; i++) { - result.push(elements[i]); - } - - for (var i=0; i < result.length; i++) { - if (result[i].close) { - result[i].close(); - } else if (result[i].parentNode) { - result[i].parentNode.removeChild(result[i]); - } - } - }; - - LiteGraph.extendClass = function(target, origin) { - for (var i in origin) { - //copy class properties - if (target.hasOwnProperty(i)) { - continue; - } - target[i] = origin[i]; - } - - if (origin.prototype) { - //copy prototype properties - for (var i in origin.prototype) { - //only enumerable - if (!origin.prototype.hasOwnProperty(i)) { - continue; - } - - if (target.prototype.hasOwnProperty(i)) { - //avoid overwriting existing ones - continue; - } - - //copy getters - if (origin.prototype.__lookupGetter__(i)) { - target.prototype.__defineGetter__( - i, - origin.prototype.__lookupGetter__(i) - ); - } else { - target.prototype[i] = origin.prototype[i]; - } - - //and setters - if (origin.prototype.__lookupSetter__(i)) { - target.prototype.__defineSetter__( - i, - origin.prototype.__lookupSetter__(i) - ); - } - } - } - }; - - //used by some widgets to render a curve editor - function CurveEditor( points ) - { - this.points = points; - this.selected = -1; - this.nearest = -1; - this.size = null; //stores last size used - this.must_update = true; - this.margin = 5; - } - - CurveEditor.sampleCurve = function(f,points) - { - if(!points) - return; - for(var i = 0; i < points.length - 1; ++i) - { - var p = points[i]; - var pn = points[i+1]; - if(pn[0] < f) - continue; - var r = (pn[0] - p[0]); - if( Math.abs(r) < 0.00001 ) - return p[1]; - var local_f = (f - p[0]) / r; - return p[1] * (1.0 - local_f) + pn[1] * local_f; - } - return 0; - } - - CurveEditor.prototype.draw = function( ctx, size, graphcanvas, background_color, line_color, inactive ) - { - var points = this.points; - if(!points) - return; - this.size = size; - var w = size[0] - this.margin * 2; - var h = size[1] - this.margin * 2; - - line_color = line_color || "#666"; - - ctx.save(); - ctx.translate(this.margin,this.margin); - - if(background_color) - { - ctx.fillStyle = "#111"; - ctx.fillRect(0,0,w,h); - ctx.fillStyle = "#222"; - ctx.fillRect(w*0.5,0,1,h); - ctx.strokeStyle = "#333"; - ctx.strokeRect(0,0,w,h); - } - ctx.strokeStyle = line_color; - if(inactive) - ctx.globalAlpha = 0.5; - ctx.beginPath(); - for(var i = 0; i < points.length; ++i) - { - var p = points[i]; - ctx.lineTo( p[0] * w, (1.0 - p[1]) * h ); - } - ctx.stroke(); - ctx.globalAlpha = 1; - if(!inactive) - for(var i = 0; i < points.length; ++i) - { - var p = points[i]; - ctx.fillStyle = this.selected == i ? "#FFF" : (this.nearest == i ? "#DDD" : "#AAA"); - ctx.beginPath(); - ctx.arc( p[0] * w, (1.0 - p[1]) * h, 2, 0, Math.PI * 2 ); - ctx.fill(); - } - ctx.restore(); - } - - //localpos is mouse in curve editor space - CurveEditor.prototype.onMouseDown = function( localpos, graphcanvas ) - { - var points = this.points; - if(!points) - return; - if( localpos[1] < 0 ) - return; - - //this.captureInput(true); - var w = this.size[0] - this.margin * 2; - var h = this.size[1] - this.margin * 2; - var x = localpos[0] - this.margin; - var y = localpos[1] - this.margin; - var pos = [x,y]; - var max_dist = 30 / graphcanvas.ds.scale; - //search closer one - this.selected = this.getCloserPoint(pos, max_dist); - //create one - if(this.selected == -1) - { - var point = [x / w, 1 - y / h]; - points.push(point); - points.sort(function(a,b){ return a[0] - b[0]; }); - this.selected = points.indexOf(point); - this.must_update = true; - } - if(this.selected != -1) - return true; - } - - CurveEditor.prototype.onMouseMove = function( localpos, graphcanvas ) - { - var points = this.points; - if(!points) - return; - var s = this.selected; - if(s < 0) - return; - var x = (localpos[0] - this.margin) / (this.size[0] - this.margin * 2 ); - var y = (localpos[1] - this.margin) / (this.size[1] - this.margin * 2 ); - var curvepos = [(localpos[0] - this.margin),(localpos[1] - this.margin)]; - var max_dist = 30 / graphcanvas.ds.scale; - this._nearest = this.getCloserPoint(curvepos, max_dist); - var point = points[s]; - if(point) - { - var is_edge_point = s == 0 || s == points.length - 1; - if( !is_edge_point && (localpos[0] < -10 || localpos[0] > this.size[0] + 10 || localpos[1] < -10 || localpos[1] > this.size[1] + 10) ) - { - points.splice(s,1); - this.selected = -1; - return; - } - if( !is_edge_point ) //not edges - point[0] = Math.clamp(x,0,1); - else - point[0] = s == 0 ? 0 : 1; - point[1] = 1.0 - Math.clamp(y,0,1); - points.sort(function(a,b){ return a[0] - b[0]; }); - this.selected = points.indexOf(point); - this.must_update = true; - } - } - - CurveEditor.prototype.onMouseUp = function( localpos, graphcanvas ) - { - this.selected = -1; - return false; - } - - CurveEditor.prototype.getCloserPoint = function(pos, max_dist) - { - var points = this.points; - if(!points) - return -1; - max_dist = max_dist || 30; - var w = (this.size[0] - this.margin * 2); - var h = (this.size[1] - this.margin * 2); - var num = points.length; - var p2 = [0,0]; - var min_dist = 1000000; - var closest = -1; - var last_valid = -1; - for(var i = 0; i < num; ++i) - { - var p = points[i]; - p2[0] = p[0] * w; - p2[1] = (1.0 - p[1]) * h; - if(p2[0] < pos[0]) - last_valid = i; - var dist = vec2.distance(pos,p2); - if(dist > min_dist || dist > max_dist) - continue; - closest = i; - min_dist = dist; - } - return closest; - } - - LiteGraph.CurveEditor = CurveEditor; - - //used to create nodes from wrapping functions - LiteGraph.getParameterNames = function(func) { - return (func + "") - .replace(/[/][/].*$/gm, "") // strip single-line comments - .replace(/\s+/g, "") // strip white space - .replace(/[/][*][^/*]*[*][/]/g, "") // strip multi-line comments /**/ - .split("){", 1)[0] - .replace(/^[^(]*[(]/, "") // extract the parameters - .replace(/=[^,]+/g, "") // strip any ES6 defaults - .split(",") - .filter(Boolean); // split & filter [""] - }; - - Math.clamp = function(v, a, b) { - return a > v ? a : b < v ? b : v; - }; - - if (typeof window != "undefined" && !window["requestAnimationFrame"]) { - window.requestAnimationFrame = - window.webkitRequestAnimationFrame || - window.mozRequestAnimationFrame || - function(callback) { - window.setTimeout(callback, 1000 / 60); - }; - } -})(this); - -if (typeof exports != "undefined") { - exports.LiteGraph = this.LiteGraph; -} - -//basic nodes -(function(global) { - var LiteGraph = global.LiteGraph; - - //Constant - function Time() { - this.addOutput("in ms", "number"); - this.addOutput("in sec", "number"); - } - - Time.title = "Time"; - Time.desc = "Time"; - - Time.prototype.onExecute = function() { - this.setOutputData(0, this.graph.globaltime * 1000); - this.setOutputData(1, this.graph.globaltime); - }; - - LiteGraph.registerNodeType("basic/time", Time); - - //Subgraph: a node that contains a graph - function Subgraph() { - var that = this; - this.size = [140, 80]; - this.properties = { enabled: true }; - this.enabled = true; - - //create inner graph - this.subgraph = new LiteGraph.LGraph(); - this.subgraph._subgraph_node = this; - this.subgraph._is_subgraph = true; - - this.subgraph.onTrigger = this.onSubgraphTrigger.bind(this); - - this.subgraph.onInputAdded = this.onSubgraphNewInput.bind(this); - this.subgraph.onInputRenamed = this.onSubgraphRenamedInput.bind(this); - this.subgraph.onInputTypeChanged = this.onSubgraphTypeChangeInput.bind( - this - ); - this.subgraph.onInputRemoved = this.onSubgraphRemovedInput.bind(this); - - this.subgraph.onOutputAdded = this.onSubgraphNewOutput.bind(this); - this.subgraph.onOutputRenamed = this.onSubgraphRenamedOutput.bind(this); - this.subgraph.onOutputTypeChanged = this.onSubgraphTypeChangeOutput.bind( - this - ); - this.subgraph.onOutputRemoved = this.onSubgraphRemovedOutput.bind(this); - } - - Subgraph.title = "Subgraph"; - Subgraph.desc = "Graph inside a node"; - Subgraph.title_color = "#334"; - - Subgraph.prototype.onGetInputs = function() { - return [["enabled", "boolean"]]; - }; - - Subgraph.prototype.onDrawTitle = function(ctx) { - if (this.flags.collapsed) { - return; - } - - ctx.fillStyle = "#555"; - var w = LiteGraph.NODE_TITLE_HEIGHT; - var x = this.size[0] - w; - ctx.fillRect(x, -w, w, w); - ctx.fillStyle = "#333"; - ctx.beginPath(); - ctx.moveTo(x + w * 0.2, -w * 0.6); - ctx.lineTo(x + w * 0.8, -w * 0.6); - ctx.lineTo(x + w * 0.5, -w * 0.3); - ctx.fill(); - }; - - Subgraph.prototype.onDblClick = function(e, pos, graphcanvas) { - var that = this; - setTimeout(function() { - graphcanvas.openSubgraph(that.subgraph); - }, 10); - }; - - Subgraph.prototype.onMouseDown = function(e, pos, graphcanvas) { - if ( - !this.flags.collapsed && - pos[0] > this.size[0] - LiteGraph.NODE_TITLE_HEIGHT && - pos[1] < 0 - ) { - var that = this; - setTimeout(function() { - graphcanvas.openSubgraph(that.subgraph); - }, 10); - } - }; - - Subgraph.prototype.onAction = function(action, param) { - this.subgraph.onAction(action, param); - }; - - Subgraph.prototype.onExecute = function() { - this.enabled = this.getInputOrProperty("enabled"); - if (!this.enabled) { - return; - } - - //send inputs to subgraph global inputs - if (this.inputs) { - for (var i = 0; i < this.inputs.length; i++) { - var input = this.inputs[i]; - var value = this.getInputData(i); - this.subgraph.setInputData(input.name, value); - } - } - - //execute - this.subgraph.runStep(); - - //send subgraph global outputs to outputs - if (this.outputs) { - for (var i = 0; i < this.outputs.length; i++) { - var output = this.outputs[i]; - var value = this.subgraph.getOutputData(output.name); - this.setOutputData(i, value); - } - } - }; - - Subgraph.prototype.sendEventToAllNodes = function(eventname, param, mode) { - if (this.enabled) { - this.subgraph.sendEventToAllNodes(eventname, param, mode); - } - }; - - //**** INPUTS *********************************** - Subgraph.prototype.onSubgraphTrigger = function(event, param) { - var slot = this.findOutputSlot(event); - if (slot != -1) { - this.triggerSlot(slot); - } - }; - - Subgraph.prototype.onSubgraphNewInput = function(name, type) { - var slot = this.findInputSlot(name); - if (slot == -1) { - //add input to the node - this.addInput(name, type); - } - }; - - Subgraph.prototype.onSubgraphRenamedInput = function(oldname, name) { - var slot = this.findInputSlot(oldname); - if (slot == -1) { - return; - } - var info = this.getInputInfo(slot); - info.name = name; - }; - - Subgraph.prototype.onSubgraphTypeChangeInput = function(name, type) { - var slot = this.findInputSlot(name); - if (slot == -1) { - return; - } - var info = this.getInputInfo(slot); - info.type = type; - }; - - Subgraph.prototype.onSubgraphRemovedInput = function(name) { - var slot = this.findInputSlot(name); - if (slot == -1) { - return; - } - this.removeInput(slot); - }; - - //**** OUTPUTS *********************************** - Subgraph.prototype.onSubgraphNewOutput = function(name, type) { - var slot = this.findOutputSlot(name); - if (slot == -1) { - this.addOutput(name, type); - } - }; - - Subgraph.prototype.onSubgraphRenamedOutput = function(oldname, name) { - var slot = this.findOutputSlot(oldname); - if (slot == -1) { - return; - } - var info = this.getOutputInfo(slot); - info.name = name; - }; - - Subgraph.prototype.onSubgraphTypeChangeOutput = function(name, type) { - var slot = this.findOutputSlot(name); - if (slot == -1) { - return; - } - var info = this.getOutputInfo(slot); - info.type = type; - }; - - Subgraph.prototype.onSubgraphRemovedOutput = function(name) { - var slot = this.findInputSlot(name); - if (slot == -1) { - return; - } - this.removeOutput(slot); - }; - // ***************************************************** - - Subgraph.prototype.getExtraMenuOptions = function(graphcanvas) { - var that = this; - return [ - { - content: "Open", - callback: function() { - graphcanvas.openSubgraph(that.subgraph); - } - } - ]; - }; - - Subgraph.prototype.onResize = function(size) { - size[1] += 20; - }; - - Subgraph.prototype.serialize = function() { - var data = LGraphNode.prototype.serialize.call(this); - data.subgraph = this.subgraph.serialize(); - return data; - }; - //no need to define node.configure, the default method detects node.subgraph and passes the object to node.subgraph.configure() - - Subgraph.prototype.clone = function() { - var node = LiteGraph.createNode(this.type); - var data = this.serialize(); - delete data["id"]; - delete data["inputs"]; - delete data["outputs"]; - node.configure(data); - return node; - }; - - LiteGraph.Subgraph = Subgraph; - LiteGraph.registerNodeType("graph/subgraph", Subgraph); - - //Input for a subgraph - function GraphInput() { - this.addOutput("", "number"); - - this.name_in_graph = ""; - this.properties = { - name: "", - type: "number", - value: 0 - }; - - var that = this; - - this.name_widget = this.addWidget( - "text", - "Name", - this.properties.name, - function(v) { - if (!v) { - return; - } - that.setProperty("name",v); - } - ); - this.type_widget = this.addWidget( - "text", - "Type", - this.properties.type, - function(v) { - that.setProperty("type",v); - } - ); - - this.value_widget = this.addWidget( - "number", - "Value", - this.properties.value, - function(v) { - that.setProperty("value",v); - } - ); - - this.widgets_up = true; - this.size = [180, 90]; - } - - GraphInput.title = "Input"; - GraphInput.desc = "Input of the graph"; - - GraphInput.prototype.onConfigure = function() - { - this.updateType(); - } - - GraphInput.prototype.updateType = function() - { - var type = this.properties.type; - this.type_widget.value = type; - if(type == "number") - { - this.value_widget.type = "number"; - this.value_widget.value = 0; - } - else if(type == "bool") - { - this.value_widget.type = "toggle"; - this.value_widget.value = true; - } - else if(type == "string") - { - this.value_widget.type = "text"; - this.value_widget.value = ""; - } - else - { - this.value_widget.type = null; - this.value_widget.value = null; - } - this.properties.value = this.value_widget.value; - } - - GraphInput.prototype.onPropertyChanged = function(name,v) - { - if( name == "name" ) - { - if (v == "" || v == this.name_in_graph || v == "enabled") { - return false; - } - if(this.graph) - { - if (this.name_in_graph) { - //already added - this.graph.renameInput( this.name_in_graph, v ); - } else { - this.graph.addInput( v, this.properties.type ); - } - } //what if not?! - this.name_widget.value = v; - this.name_in_graph = v; - } - else if( name == "type" ) - { - v = v || ""; - this.updateType(v); - } - else if( name == "value" ) - { - } - } - - GraphInput.prototype.getTitle = function() { - if (this.flags.collapsed) { - return this.properties.name; - } - return this.title; - }; - - GraphInput.prototype.onAction = function(action, param) { - if (this.properties.type == LiteGraph.EVENT) { - this.triggerSlot(0, param); - } - }; - - GraphInput.prototype.onExecute = function() { - var name = this.properties.name; - //read from global input - var data = this.graph.inputs[name]; - if (!data) { - this.setOutputData(0, this.properties.value ); - } - this.setOutputData(0, data.value === undefined ? this.properties.value : data.value); - }; - - GraphInput.prototype.onRemoved = function() { - if (this.name_in_graph) { - this.graph.removeInput(this.name_in_graph); - } - }; - - LiteGraph.GraphInput = GraphInput; - LiteGraph.registerNodeType("graph/input", GraphInput); - - //Output for a subgraph - function GraphOutput() { - this.addInput("", ""); - - this.name_in_graph = ""; - this.properties = {}; - var that = this; - - Object.defineProperty(this.properties, "name", { - get: function() { - return that.name_in_graph; - }, - set: function(v) { - if (v == "" || v == that.name_in_graph) { - return; - } - if (that.name_in_graph) { - //already added - that.graph.renameOutput(that.name_in_graph, v); - } else { - that.graph.addOutput(v, that.properties.type); - } - that.name_widget.value = v; - that.name_in_graph = v; - }, - enumerable: true - }); - - Object.defineProperty(this.properties, "type", { - get: function() { - return that.inputs[0].type; - }, - set: function(v) { - if (v == "action" || v == "event") { - v = LiteGraph.ACTION; - } - that.inputs[0].type = v; - if (that.name_in_graph) { - //already added - that.graph.changeOutputType( - that.name_in_graph, - that.inputs[0].type - ); - } - that.type_widget.value = v || ""; - }, - enumerable: true - }); - - this.name_widget = this.addWidget( - "text", - "Name", - this.properties.name, - function(v) { - if (!v) { - return; - } - that.properties.name = v; - } - ); - this.type_widget = this.addWidget( - "text", - "Type", - this.properties.type, - function(v) { - v = v || ""; - that.properties.type = v; - } - ); - - this.widgets_up = true; - this.size = [180, 60]; - } - - GraphOutput.title = "Output"; - GraphOutput.desc = "Output of the graph"; - - GraphOutput.prototype.onExecute = function() { - this._value = this.getInputData(0); - this.graph.setOutputData(this.properties.name, this._value); - }; - - GraphOutput.prototype.onAction = function(action, param) { - if (this.properties.type == LiteGraph.ACTION) { - this.graph.trigger(this.properties.name, param); - } - }; - - GraphOutput.prototype.onRemoved = function() { - if (this.name_in_graph) { - this.graph.removeOutput(this.name_in_graph); - } - }; - - GraphOutput.prototype.getTitle = function() { - if (this.flags.collapsed) { - return this.properties.name; - } - return this.title; - }; - - LiteGraph.GraphOutput = GraphOutput; - LiteGraph.registerNodeType("graph/output", GraphOutput); - - //Constant - function ConstantNumber() { - this.addOutput("value", "number"); - this.addProperty("value", 1.0); - } - - ConstantNumber.title = "Const Number"; - ConstantNumber.desc = "Constant number"; - - ConstantNumber.prototype.onExecute = function() { - this.setOutputData(0, parseFloat(this.properties["value"])); - }; - - ConstantNumber.prototype.getTitle = function() { - if (this.flags.collapsed) { - return this.properties.value; - } - return this.title; - }; - - ConstantNumber.prototype.setValue = function(v) { - this.properties.value = v; - }; - - ConstantNumber.prototype.onDrawBackground = function(ctx) { - //show the current value - this.outputs[0].label = this.properties["value"].toFixed(3); - }; - - LiteGraph.registerNodeType("basic/const", ConstantNumber); - - function ConstantString() { - this.addOutput("", "string"); - this.addProperty("value", ""); - this.widget = this.addWidget( - "text", - "value", - "", - this.setValue.bind(this) - ); - this.widgets_up = true; - this.size = [100, 30]; - } - - ConstantString.title = "Const String"; - ConstantString.desc = "Constant string"; - - ConstantString.prototype.setValue = function(v) { - this.properties.value = v; - }; - - ConstantString.prototype.onPropertyChanged = function(name, value) { - this.widget.value = value; - }; - - ConstantString.prototype.getTitle = ConstantNumber.prototype.getTitle; - - ConstantString.prototype.onExecute = function() { - this.setOutputData(0, this.properties["value"]); - }; - - LiteGraph.registerNodeType("basic/string", ConstantString); - - function ConstantData() { - this.addOutput("", ""); - this.addProperty("value", ""); - this.widget = this.addWidget( - "text", - "json", - "", - this.setValue.bind(this) - ); - this.widgets_up = true; - this.size = [140, 30]; - this._value = null; - } - - ConstantData.title = "Const Data"; - ConstantData.desc = "Constant Data"; - - ConstantData.prototype.setValue = function(v) { - this.properties.value = v; - this.onPropertyChanged("value", v); - }; - - ConstantData.prototype.onPropertyChanged = function(name, value) { - this.widget.value = value; - if (value == null || value == "") { - return; - } - - try { - this._value = JSON.parse(value); - this.boxcolor = "#AEA"; - } catch (err) { - this.boxcolor = "red"; - } - }; - - ConstantData.prototype.onExecute = function() { - this.setOutputData(0, this._value); - }; - - LiteGraph.registerNodeType("basic/data", ConstantData); - - function ObjectProperty() { - this.addInput("obj", ""); - this.addOutput("", ""); - this.addProperty("value", ""); - this.widget = this.addWidget( - "text", - "prop.", - "", - this.setValue.bind(this) - ); - this.widgets_up = true; - this.size = [140, 30]; - this._value = null; - } - - ObjectProperty.title = "Object property"; - ObjectProperty.desc = "Outputs the property of an object"; - - ObjectProperty.prototype.setValue = function(v) { - this.properties.value = v; - this.widget.value = v; - }; - - ObjectProperty.prototype.getTitle = function() { - if (this.flags.collapsed) { - return "in." + this.properties.value; - } - return this.title; - }; - - ObjectProperty.prototype.onPropertyChanged = function(name, value) { - this.widget.value = value; - }; - - ObjectProperty.prototype.onExecute = function() { - var data = this.getInputData(0); - if (data != null) { - this.setOutputData(0, data[this.properties.value]); - } - }; - - LiteGraph.registerNodeType("basic/object_property", ObjectProperty); - - function ObjectKeys() { - this.addInput("obj", ""); - this.addOutput("keys", "array"); - this.size = [140, 30]; - } - - ObjectKeys.title = "Object keys"; - ObjectKeys.desc = "Outputs an array with the keys of an object"; - - ObjectKeys.prototype.onExecute = function() { - var data = this.getInputData(0); - if (data != null) { - this.setOutputData(0, Object.keys(data) ); - } - }; - - LiteGraph.registerNodeType("basic/object_keys", ObjectKeys); - - function MergeObjects() { - this.addInput("A", "object"); - this.addInput("B", "object"); - this.addOutput("", "object"); - this._result = {}; - var that = this; - this.addWidget("button","clear","",function(){ - that._result = {}; - }); - this.size = this.computeSize(); - } - - MergeObjects.title = "Merge Objects"; - MergeObjects.desc = "Creates an object copying properties from others"; - - MergeObjects.prototype.onExecute = function() { - var A = this.getInputData(0); - var B = this.getInputData(1); - var C = this._result; - if(A) - for(var i in A) - C[i] = A[i]; - if(B) - for(var i in B) - C[i] = B[i]; - this.setOutputData(0,C); - }; - - LiteGraph.registerNodeType("basic/merge_objects", MergeObjects ); - - //Store as variable - function Variable() { - this.size = [60, 30]; - this.addInput("in"); - this.addOutput("out"); - this.properties = { varname: "myname", global: false }; - this.value = null; - } - - Variable.title = "Variable"; - Variable.desc = "store/read variable value"; - - Variable.prototype.onExecute = function() { - this.value = this.getInputData(0); - if(this.graph) - this.graph.vars[ this.properties.varname ] = this.value; - if(this.properties.global) - global[this.properties.varname] = this.value; - this.setOutputData(0, this.value ); - }; - - Variable.prototype.getTitle = function() { - return this.properties.varname; - }; - - LiteGraph.registerNodeType("basic/variable", Variable); - - - function DownloadData() { - this.size = [60, 30]; - this.addInput("data", 0 ); - this.addInput("download", LiteGraph.ACTION ); - this.properties = { filename: "data.json" }; - this.value = null; - var that = this; - this.addWidget("button","Download","", function(v){ - if(!that.value) - return; - that.downloadAsFile(); - }); - } - - DownloadData.title = "Download"; - DownloadData.desc = "Download some data"; - - DownloadData.prototype.downloadAsFile = function() - { - if(this.value == null) - return; - - var str = null; - if(this.value.constructor === String) - str = this.value; - else - str = JSON.stringify(this.value); - - var file = new Blob([str]); - var url = URL.createObjectURL( file ); - var element = document.createElement("a"); - element.setAttribute('href', url); - element.setAttribute('download', this.properties.filename ); - element.style.display = 'none'; - document.body.appendChild(element); - element.click(); - document.body.removeChild(element); - setTimeout( function(){ URL.revokeObjectURL( url ); }, 1000*60 ); //wait one minute to revoke url - } - - DownloadData.prototype.onAction = function(action, param) { - var that = this; - setTimeout( function(){ that.downloadAsFile(); }, 100); //deferred to avoid blocking the renderer with the popup - } - - DownloadData.prototype.onExecute = function() { - if (this.inputs[0]) { - this.value = this.getInputData(0); - } - }; - - DownloadData.prototype.getTitle = function() { - if (this.flags.collapsed) { - return this.properties.filename; - } - return this.title; - }; - - LiteGraph.registerNodeType("basic/download", DownloadData); - - - - //Watch a value in the editor - function Watch() { - this.size = [60, 30]; - this.addInput("value", 0, { label: "" }); - this.value = 0; - } - - Watch.title = "Watch"; - Watch.desc = "Show value of input"; - - Watch.prototype.onExecute = function() { - if (this.inputs[0]) { - this.value = this.getInputData(0); - } - }; - - Watch.prototype.getTitle = function() { - if (this.flags.collapsed) { - return this.inputs[0].label; - } - return this.title; - }; - - Watch.toString = function(o) { - if (o == null) { - return "null"; - } else if (o.constructor === Number) { - return o.toFixed(3); - } else if (o.constructor === Array) { - var str = "["; - for (var i = 0; i < o.length; ++i) { - str += Watch.toString(o[i]) + (i + 1 != o.length ? "," : ""); - } - str += "]"; - return str; - } else { - return String(o); - } - }; - - Watch.prototype.onDrawBackground = function(ctx) { - //show the current value - this.inputs[0].label = Watch.toString(this.value); - }; - - LiteGraph.registerNodeType("basic/watch", Watch); - - //in case one type doesnt match other type but you want to connect them anyway - function Cast() { - this.addInput("in", 0); - this.addOutput("out", 0); - this.size = [40, 30]; - } - - Cast.title = "Cast"; - Cast.desc = "Allows to connect different types"; - - Cast.prototype.onExecute = function() { - this.setOutputData(0, this.getInputData(0)); - }; - - LiteGraph.registerNodeType("basic/cast", Cast); - - //Show value inside the debug console - function Console() { - this.mode = LiteGraph.ON_EVENT; - this.size = [80, 30]; - this.addProperty("msg", ""); - this.addInput("log", LiteGraph.EVENT); - this.addInput("msg", 0); - } - - Console.title = "Console"; - Console.desc = "Show value inside the console"; - - Console.prototype.onAction = function(action, param) { - if (action == "log") { - console.log(param); - } else if (action == "warn") { - console.warn(param); - } else if (action == "error") { - console.error(param); - } - }; - - Console.prototype.onExecute = function() { - var msg = this.getInputData(1); - if (msg !== null) { - this.properties.msg = msg; - } - console.log(msg); - }; - - Console.prototype.onGetInputs = function() { - return [ - ["log", LiteGraph.ACTION], - ["warn", LiteGraph.ACTION], - ["error", LiteGraph.ACTION] - ]; - }; - - LiteGraph.registerNodeType("basic/console", Console); - - //Show value inside the debug console - function Alert() { - this.mode = LiteGraph.ON_EVENT; - this.addProperty("msg", ""); - this.addInput("", LiteGraph.EVENT); - var that = this; - this.widget = this.addWidget("text", "Text", "", function(v) { - that.properties.msg = v; - }); - this.widgets_up = true; - this.size = [200, 30]; - } - - Alert.title = "Alert"; - Alert.desc = "Show an alert window"; - Alert.color = "#510"; - - Alert.prototype.onConfigure = function(o) { - this.widget.value = o.properties.msg; - }; - - Alert.prototype.onAction = function(action, param) { - var msg = this.properties.msg; - setTimeout(function() { - alert(msg); - }, 10); - }; - - LiteGraph.registerNodeType("basic/alert", Alert); - - //Execites simple code - function NodeScript() { - this.size = [60, 30]; - this.addProperty("onExecute", "return A;"); - this.addInput("A", ""); - this.addInput("B", ""); - this.addOutput("out", ""); - - this._func = null; - this.data = {}; - } - - NodeScript.prototype.onConfigure = function(o) { - if (o.properties.onExecute && LiteGraph.allow_scripts) - this.compileCode(o.properties.onExecute); - else - console.warn("Script not compiled, LiteGraph.allow_scripts is false"); - }; - - NodeScript.title = "Script"; - NodeScript.desc = "executes a code (max 100 characters)"; - - NodeScript.widgets_info = { - onExecute: { type: "code" } - }; - - NodeScript.prototype.onPropertyChanged = function(name, value) { - if (name == "onExecute" && LiteGraph.allow_scripts) - this.compileCode(value); - else - console.warn("Script not compiled, LiteGraph.allow_scripts is false"); - }; - - NodeScript.prototype.compileCode = function(code) { - this._func = null; - if (code.length > 256) { - console.warn("Script too long, max 256 chars"); - } else { - var code_low = code.toLowerCase(); - var forbidden_words = [ - "script", - "body", - "document", - "eval", - "nodescript", - "function" - ]; //bad security solution - for (var i = 0; i < forbidden_words.length; ++i) { - if (code_low.indexOf(forbidden_words[i]) != -1) { - console.warn("invalid script"); - return; - } - } - try { - this._func = new Function("A", "B", "C", "DATA", "node", code); - } catch (err) { - console.error("Error parsing script"); - console.error(err); - } - } - }; - - NodeScript.prototype.onExecute = function() { - if (!this._func) { - return; - } - - try { - var A = this.getInputData(0); - var B = this.getInputData(1); - var C = this.getInputData(2); - this.setOutputData(0, this._func(A, B, C, this.data, this)); - } catch (err) { - console.error("Error in script"); - console.error(err); - } - }; - - NodeScript.prototype.onGetOutputs = function() { - return [["C", ""]]; - }; - - 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, 30]; - 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); - - //convert to Event if the value is true - function TriggerEvent() { - this.size = [60, 30]; - this.addInput("in", ""); - this.addOutput("true", LiteGraph.EVENT); - this.addOutput("change", LiteGraph.EVENT); - this.was_true = false; - } - - TriggerEvent.title = "TriggerEvent"; - TriggerEvent.desc = "Triggers event if value is true"; - - TriggerEvent.prototype.onExecute = function(action, param) { - var v = this.getInputData(0); - if(v) - this.triggerSlot(0, param); - if(v && !this.was_true) - this.triggerSlot(1, param); - this.was_true = v; - }; - - LiteGraph.registerNodeType("events/trigger", TriggerEvent); - - //Sequencer for events - function Sequencer() { - this.addInput("", LiteGraph.ACTION); - this.addInput("", LiteGraph.ACTION); - this.addInput("", LiteGraph.ACTION); - this.addInput("", LiteGraph.ACTION); - this.addInput("", LiteGraph.ACTION); - this.addInput("", LiteGraph.ACTION); - this.addOutput("", LiteGraph.EVENT); - this.addOutput("", LiteGraph.EVENT); - this.addOutput("", LiteGraph.EVENT); - this.addOutput("", LiteGraph.EVENT); - this.addOutput("", LiteGraph.EVENT); - this.addOutput("", LiteGraph.EVENT); - this.size = [120, 30]; - this.flags = { horizontal: true, render_box: false }; - } - - Sequencer.title = "Sequencer"; - Sequencer.desc = "Trigger events when an event arrives"; - - Sequencer.prototype.getTitle = function() { - return ""; - }; - - Sequencer.prototype.onAction = function(action, param) { - if (this.outputs) { - for (var i = 0; i < this.outputs.length; ++i) { - this.triggerSlot(i, param); - } - } - }; - - LiteGraph.registerNodeType("events/sequencer", Sequencer); - - //Filter events - function FilterEvent() { - this.size = [60, 30]; - 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 EventCounter() { - this.addInput("inc", LiteGraph.ACTION); - this.addInput("dec", LiteGraph.ACTION); - this.addInput("reset", LiteGraph.ACTION); - this.addOutput("change", LiteGraph.EVENT); - this.addOutput("num", "number"); - this.num = 0; - } - - EventCounter.title = "Counter"; - EventCounter.desc = "Counts events"; - - EventCounter.prototype.getTitle = function() { - if (this.flags.collapsed) { - return String(this.num); - } - return this.title; - }; - - EventCounter.prototype.onAction = function(action, param) { - var v = this.num; - if (action == "inc") { - this.num += 1; - } else if (action == "dec") { - this.num -= 1; - } else if (action == "reset") { - this.num = 0; - } - if (this.num != v) { - this.trigger("change", this.num); - } - }; - - EventCounter.prototype.onDrawBackground = function(ctx) { - if (this.flags.collapsed) { - return; - } - ctx.fillStyle = "#AAA"; - ctx.font = "20px Arial"; - ctx.textAlign = "center"; - ctx.fillText(this.num, this.size[0] * 0.5, this.size[1] * 0.5); - }; - - EventCounter.prototype.onExecute = function() { - this.setOutputData(1, this.num); - }; - - LiteGraph.registerNodeType("events/counter", EventCounter); - - //Show value inside the debug console - function DelayEvent() { - this.size = [60, 30]; - this.addProperty("time_in_ms", 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) { - var time = this.properties.time_in_ms; - if (time <= 0) { - this.trigger(null, param); - } else { - this._pending.push([time, param]); - } - }; - - DelayEvent.prototype.onExecute = function() { - var dt = this.graph.elapsed_time * 1000; //in ms - - if (this.isInputConnected(1)) { - this.properties.time_in_ms = this.getInputData(1); - } - - 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], ["time_in_ms", "number"]]; - }; - - 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 - - var trigger = this.time == 0; - - this.time += dt; - this.last_interval = Math.max( - 1, - this.getInputOrProperty("interval") | 0 - ); - - if ( - !trigger && - (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); - - function DataStore() { - this.addInput("data", ""); - this.addInput("assign", LiteGraph.ACTION); - this.addOutput("data", ""); - this._last_value = null; - this.properties = { data: null, serialize: true }; - var that = this; - this.addWidget("button","store","",function(){ - that.properties.data = that._last_value; - }); - } - - DataStore.title = "Data Store"; - DataStore.desc = "Stores data and only changes when event is received"; - - DataStore.prototype.onExecute = function() - { - this._last_value = this.getInputData(0); - this.setOutputData(0, this.properties.data ); - } - - DataStore.prototype.onAction = function(action, param) { - this.properties.data = this._last_value; - }; - - DataStore.prototype.onSerialize = function(o) - { - if(o.data == null) - return; - if(this.properties.serialize == false || (o.data.constructor !== String && o.data.constructor !== Number && o.data.constructor !== Boolean && o.data.constructor !== Array && o.data.constructor !== Object )) - o.data = null; - } - - LiteGraph.registerNodeType("basic/data_store", DataStore); -})(this); - -//widgets -(function(global) { - var LiteGraph = global.LiteGraph; - - /* Button ****************/ - - function WidgetButton() { - this.addOutput("", LiteGraph.EVENT); - this.addOutput("", "boolean"); - this.addProperty("text", "click me"); - this.addProperty("font_size", 30); - this.addProperty("message", ""); - this.size = [164, 84]; - this.clicked = false; - } - - WidgetButton.title = "Button"; - WidgetButton.desc = "Triggers an event"; - - WidgetButton.font = "Arial"; - WidgetButton.prototype.onDrawForeground = function(ctx) { - if (this.flags.collapsed) { - return; - } - var margin = 10; - ctx.fillStyle = "black"; - ctx.fillRect( - margin + 1, - margin + 1, - this.size[0] - margin * 2, - this.size[1] - margin * 2 - ); - ctx.fillStyle = "#AAF"; - ctx.fillRect( - margin - 1, - margin - 1, - this.size[0] - margin * 2, - this.size[1] - margin * 2 - ); - ctx.fillStyle = this.clicked - ? "white" - : this.mouseOver - ? "#668" - : "#334"; - ctx.fillRect( - margin, - margin, - this.size[0] - margin * 2, - this.size[1] - margin * 2 - ); - - if (this.properties.text || this.properties.text === 0) { - var font_size = this.properties.font_size || 30; - ctx.textAlign = "center"; - ctx.fillStyle = this.clicked ? "black" : "white"; - ctx.font = font_size + "px " + WidgetButton.font; - ctx.fillText( - this.properties.text, - this.size[0] * 0.5, - this.size[1] * 0.5 + font_size * 0.3 - ); - ctx.textAlign = "left"; - } - }; - - WidgetButton.prototype.onMouseDown = function(e, local_pos) { - if ( - local_pos[0] > 1 && - local_pos[1] > 1 && - local_pos[0] < this.size[0] - 2 && - local_pos[1] < this.size[1] - 2 - ) { - this.clicked = true; - this.triggerSlot(0, this.properties.message); - return true; - } - }; - - WidgetButton.prototype.onExecute = function() { - this.setOutputData(1, this.clicked); - }; - - WidgetButton.prototype.onMouseUp = function(e) { - this.clicked = false; - }; - - LiteGraph.registerNodeType("widget/button", WidgetButton); - - function WidgetToggle() { - this.addInput("", "boolean"); - this.addInput("e", LiteGraph.ACTION); - this.addOutput("v", "boolean"); - this.addOutput("e", LiteGraph.EVENT); - this.properties = { font: "", value: false }; - this.size = [160, 44]; - } - - WidgetToggle.title = "Toggle"; - WidgetToggle.desc = "Toggles between true or false"; - - WidgetToggle.prototype.onDrawForeground = function(ctx) { - if (this.flags.collapsed) { - return; - } - - var size = this.size[1] * 0.5; - var margin = 0.25; - var h = this.size[1] * 0.8; - ctx.font = this.properties.font || (size * 0.8).toFixed(0) + "px Arial"; - var w = ctx.measureText(this.title).width; - var x = (this.size[0] - (w + size)) * 0.5; - - ctx.fillStyle = "#AAA"; - ctx.fillRect(x, h - size, size, size); - - ctx.fillStyle = this.properties.value ? "#AEF" : "#000"; - ctx.fillRect( - x + size * margin, - h - size + size * margin, - size * (1 - margin * 2), - size * (1 - margin * 2) - ); - - ctx.textAlign = "left"; - ctx.fillStyle = "#AAA"; - ctx.fillText(this.title, size * 1.2 + x, h * 0.85); - ctx.textAlign = "left"; - }; - - WidgetToggle.prototype.onAction = function(action) { - this.properties.value = !this.properties.value; - this.trigger("e", this.properties.value); - }; - - WidgetToggle.prototype.onExecute = function() { - var v = this.getInputData(0); - if (v != null) { - this.properties.value = v; - } - this.setOutputData(0, this.properties.value); - }; - - WidgetToggle.prototype.onMouseDown = function(e, local_pos) { - if ( - local_pos[0] > 1 && - local_pos[1] > 1 && - local_pos[0] < this.size[0] - 2 && - local_pos[1] < this.size[1] - 2 - ) { - this.properties.value = !this.properties.value; - this.graph._version++; - this.trigger("e", this.properties.value); - return true; - } - }; - - LiteGraph.registerNodeType("widget/toggle", WidgetToggle); - - /* Number ****************/ - - function WidgetNumber() { - this.addOutput("", "number"); - this.size = [80, 60]; - this.properties = { min: -1000, max: 1000, value: 1, step: 1 }; - this.old_y = -1; - this._remainder = 0; - this._precision = 0; - this.mouse_captured = false; - } - - WidgetNumber.title = "Number"; - WidgetNumber.desc = "Widget to select number value"; - - WidgetNumber.pixels_threshold = 10; - WidgetNumber.markers_color = "#666"; - - WidgetNumber.prototype.onDrawForeground = function(ctx) { - var x = this.size[0] * 0.5; - var h = this.size[1]; - if (h > 30) { - ctx.fillStyle = WidgetNumber.markers_color; - ctx.beginPath(); - ctx.moveTo(x, h * 0.1); - ctx.lineTo(x + h * 0.1, h * 0.2); - ctx.lineTo(x + h * -0.1, h * 0.2); - ctx.fill(); - ctx.beginPath(); - ctx.moveTo(x, h * 0.9); - ctx.lineTo(x + h * 0.1, h * 0.8); - ctx.lineTo(x + h * -0.1, h * 0.8); - ctx.fill(); - ctx.font = (h * 0.7).toFixed(1) + "px Arial"; - } else { - ctx.font = (h * 0.8).toFixed(1) + "px Arial"; - } - - ctx.textAlign = "center"; - ctx.font = (h * 0.7).toFixed(1) + "px Arial"; - ctx.fillStyle = "#EEE"; - ctx.fillText( - this.properties.value.toFixed(this._precision), - x, - h * 0.75 - ); - }; - - WidgetNumber.prototype.onExecute = function() { - this.setOutputData(0, this.properties.value); - }; - - WidgetNumber.prototype.onPropertyChanged = function(name, value) { - var t = (this.properties.step + "").split("."); - this._precision = t.length > 1 ? t[1].length : 0; - }; - - WidgetNumber.prototype.onMouseDown = function(e, pos) { - if (pos[1] < 0) { - return; - } - - this.old_y = e.canvasY; - this.captureInput(true); - this.mouse_captured = true; - - return true; - }; - - WidgetNumber.prototype.onMouseMove = function(e) { - if (!this.mouse_captured) { - return; - } - - var delta = this.old_y - e.canvasY; - if (e.shiftKey) { - delta *= 10; - } - if (e.metaKey || e.altKey) { - delta *= 0.1; - } - this.old_y = e.canvasY; - - var steps = this._remainder + delta / WidgetNumber.pixels_threshold; - this._remainder = steps % 1; - steps = steps | 0; - - var v = Math.clamp( - this.properties.value + steps * this.properties.step, - this.properties.min, - this.properties.max - ); - this.properties.value = v; - this.graph._version++; - this.setDirtyCanvas(true); - }; - - WidgetNumber.prototype.onMouseUp = function(e, pos) { - if (e.click_time < 200) { - var steps = pos[1] > this.size[1] * 0.5 ? -1 : 1; - this.properties.value = Math.clamp( - this.properties.value + steps * this.properties.step, - this.properties.min, - this.properties.max - ); - this.graph._version++; - this.setDirtyCanvas(true); - } - - if (this.mouse_captured) { - this.mouse_captured = false; - this.captureInput(false); - } - }; - - LiteGraph.registerNodeType("widget/number", WidgetNumber); - - - /* Combo ****************/ - - function WidgetCombo() { - this.addOutput("", "string"); - this.addOutput("change", LiteGraph.EVENT); - this.size = [80, 60]; - this.properties = { value: "A", values:"A;B;C" }; - this.old_y = -1; - this.mouse_captured = false; - this._values = this.properties.values.split(";"); - var that = this; - this.widgets_up = true; - this.widget = this.addWidget("combo","", this.properties.value, function(v){ - that.properties.value = v; - that.triggerSlot(1, v); - }, { property: "value", values: this._values } ); - } - - WidgetCombo.title = "Combo"; - WidgetCombo.desc = "Widget to select from a list"; - - WidgetCombo.prototype.onExecute = function() { - this.setOutputData( 0, this.properties.value ); - }; - - WidgetCombo.prototype.onPropertyChanged = function(name, value) { - if(name == "values") - { - this._values = value.split(";"); - this.widget.options.values = this._values; - } - else if(name == "value") - { - this.widget.value = value; - } - }; - - LiteGraph.registerNodeType("widget/combo", WidgetCombo); - - - /* Knob ****************/ - - function WidgetKnob() { - this.addOutput("", "number"); - this.size = [64, 84]; - this.properties = { - min: 0, - max: 1, - value: 0.5, - color: "#7AF", - precision: 2 - }; - this.value = -1; - } - - WidgetKnob.title = "Knob"; - WidgetKnob.desc = "Circular controller"; - WidgetKnob.size = [80, 100]; - - WidgetKnob.prototype.onDrawForeground = function(ctx) { - if (this.flags.collapsed) { - return; - } - - if (this.value == -1) { - this.value = - (this.properties.value - this.properties.min) / - (this.properties.max - this.properties.min); - } - - var center_x = this.size[0] * 0.5; - var center_y = this.size[1] * 0.5; - var radius = Math.min(this.size[0], this.size[1]) * 0.5 - 5; - var w = Math.floor(radius * 0.05); - - ctx.globalAlpha = 1; - ctx.save(); - ctx.translate(center_x, center_y); - ctx.rotate(Math.PI * 0.75); - - //bg - ctx.fillStyle = "rgba(0,0,0,0.5)"; - ctx.beginPath(); - ctx.moveTo(0, 0); - ctx.arc(0, 0, radius, 0, Math.PI * 1.5); - ctx.fill(); - - //value - ctx.strokeStyle = "black"; - ctx.fillStyle = this.properties.color; - ctx.lineWidth = 2; - ctx.beginPath(); - ctx.moveTo(0, 0); - ctx.arc( - 0, - 0, - radius - 4, - 0, - Math.PI * 1.5 * Math.max(0.01, this.value) - ); - ctx.closePath(); - ctx.fill(); - //ctx.stroke(); - ctx.lineWidth = 1; - ctx.globalAlpha = 1; - ctx.restore(); - - //inner - ctx.fillStyle = "black"; - ctx.beginPath(); - ctx.arc(center_x, center_y, radius * 0.75, 0, Math.PI * 2, true); - ctx.fill(); - - //miniball - ctx.fillStyle = this.mouseOver ? "white" : this.properties.color; - ctx.beginPath(); - var angle = this.value * Math.PI * 1.5 + Math.PI * 0.75; - ctx.arc( - center_x + Math.cos(angle) * radius * 0.65, - center_y + Math.sin(angle) * radius * 0.65, - radius * 0.05, - 0, - Math.PI * 2, - true - ); - ctx.fill(); - - //text - ctx.fillStyle = this.mouseOver ? "white" : "#AAA"; - ctx.font = Math.floor(radius * 0.5) + "px Arial"; - ctx.textAlign = "center"; - ctx.fillText( - this.properties.value.toFixed(this.properties.precision), - center_x, - center_y + radius * 0.15 - ); - }; - - WidgetKnob.prototype.onExecute = function() { - this.setOutputData(0, this.properties.value); - this.boxcolor = LiteGraph.colorToString([ - this.value, - this.value, - this.value - ]); - }; - - WidgetKnob.prototype.onMouseDown = function(e) { - this.center = [this.size[0] * 0.5, this.size[1] * 0.5 + 20]; - this.radius = this.size[0] * 0.5; - if ( - e.canvasY - this.pos[1] < 20 || - LiteGraph.distance( - [e.canvasX, e.canvasY], - [this.pos[0] + this.center[0], this.pos[1] + this.center[1]] - ) > this.radius - ) { - return false; - } - this.oldmouse = [e.canvasX - this.pos[0], e.canvasY - this.pos[1]]; - this.captureInput(true); - return true; - }; - - WidgetKnob.prototype.onMouseMove = function(e) { - if (!this.oldmouse) { - return; - } - - var m = [e.canvasX - this.pos[0], e.canvasY - this.pos[1]]; - - var v = this.value; - v -= (m[1] - this.oldmouse[1]) * 0.01; - if (v > 1.0) { - v = 1.0; - } else if (v < 0.0) { - v = 0.0; - } - this.value = v; - this.properties.value = - this.properties.min + - (this.properties.max - this.properties.min) * this.value; - this.oldmouse = m; - this.setDirtyCanvas(true); - }; - - WidgetKnob.prototype.onMouseUp = function(e) { - if (this.oldmouse) { - this.oldmouse = null; - this.captureInput(false); - } - }; - - WidgetKnob.prototype.onPropertyChanged = function(name, value) { - if (name == "min" || name == "max" || name == "value") { - this.properties[name] = parseFloat(value); - return true; //block - } - }; - - LiteGraph.registerNodeType("widget/knob", WidgetKnob); - - //Show value inside the debug console - function WidgetSliderGUI() { - this.addOutput("", "number"); - this.properties = { - value: 0.5, - min: 0, - max: 1, - text: "V" - }; - var that = this; - this.size = [140, 40]; - this.slider = this.addWidget( - "slider", - "V", - this.properties.value, - function(v) { - that.properties.value = v; - }, - this.properties - ); - this.widgets_up = true; - } - - WidgetSliderGUI.title = "Inner Slider"; - - WidgetSliderGUI.prototype.onPropertyChanged = function(name, value) { - if (name == "value") { - this.slider.value = value; - } - }; - - WidgetSliderGUI.prototype.onExecute = function() { - this.setOutputData(0, this.properties.value); - }; - - LiteGraph.registerNodeType("widget/internal_slider", WidgetSliderGUI); - - //Widget H SLIDER - function WidgetHSlider() { - this.size = [160, 26]; - this.addOutput("", "number"); - this.properties = { color: "#7AF", min: 0, max: 1, value: 0.5 }; - this.value = -1; - } - - WidgetHSlider.title = "H.Slider"; - WidgetHSlider.desc = "Linear slider controller"; - - WidgetHSlider.prototype.onDrawForeground = function(ctx) { - if (this.value == -1) { - this.value = - (this.properties.value - this.properties.min) / - (this.properties.max - this.properties.min); - } - - //border - ctx.globalAlpha = 1; - ctx.lineWidth = 1; - ctx.fillStyle = "#000"; - ctx.fillRect(2, 2, this.size[0] - 4, this.size[1] - 4); - - ctx.fillStyle = this.properties.color; - ctx.beginPath(); - ctx.rect(4, 4, (this.size[0] - 8) * this.value, this.size[1] - 8); - ctx.fill(); - }; - - WidgetHSlider.prototype.onExecute = function() { - this.properties.value = - this.properties.min + - (this.properties.max - this.properties.min) * this.value; - this.setOutputData(0, this.properties.value); - this.boxcolor = LiteGraph.colorToString([ - this.value, - this.value, - this.value - ]); - }; - - WidgetHSlider.prototype.onMouseDown = function(e) { - if (e.canvasY - this.pos[1] < 0) { - return false; - } - - this.oldmouse = [e.canvasX - this.pos[0], e.canvasY - this.pos[1]]; - this.captureInput(true); - return true; - }; - - WidgetHSlider.prototype.onMouseMove = function(e) { - if (!this.oldmouse) { - return; - } - - var m = [e.canvasX - this.pos[0], e.canvasY - this.pos[1]]; - - var v = this.value; - var delta = m[0] - this.oldmouse[0]; - v += delta / this.size[0]; - if (v > 1.0) { - v = 1.0; - } else if (v < 0.0) { - v = 0.0; - } - - this.value = v; - - this.oldmouse = m; - this.setDirtyCanvas(true); - }; - - WidgetHSlider.prototype.onMouseUp = function(e) { - this.oldmouse = null; - this.captureInput(false); - }; - - WidgetHSlider.prototype.onMouseLeave = function(e) { - //this.oldmouse = null; - }; - - LiteGraph.registerNodeType("widget/hslider", WidgetHSlider); - - function WidgetProgress() { - this.size = [160, 26]; - this.addInput("", "number"); - this.properties = { min: 0, max: 1, value: 0, color: "#AAF" }; - } - - WidgetProgress.title = "Progress"; - WidgetProgress.desc = "Shows data in linear progress"; - - WidgetProgress.prototype.onExecute = function() { - var v = this.getInputData(0); - if (v != undefined) { - this.properties["value"] = v; - } - }; - - WidgetProgress.prototype.onDrawForeground = function(ctx) { - //border - ctx.lineWidth = 1; - ctx.fillStyle = this.properties.color; - var v = - (this.properties.value - this.properties.min) / - (this.properties.max - this.properties.min); - v = Math.min(1, v); - v = Math.max(0, v); - ctx.fillRect(2, 2, (this.size[0] - 4) * v, this.size[1] - 4); - }; - - LiteGraph.registerNodeType("widget/progress", WidgetProgress); - - function WidgetText() { - this.addInputs("", 0); - this.properties = { - value: "...", - font: "Arial", - fontsize: 18, - color: "#AAA", - align: "left", - glowSize: 0, - decimals: 1 - }; - } - - WidgetText.title = "Text"; - WidgetText.desc = "Shows the input value"; - WidgetText.widgets = [ - { name: "resize", text: "Resize box", type: "button" }, - { name: "led_text", text: "LED", type: "minibutton" }, - { name: "normal_text", text: "Normal", type: "minibutton" } - ]; - - WidgetText.prototype.onDrawForeground = function(ctx) { - //ctx.fillStyle="#000"; - //ctx.fillRect(0,0,100,60); - ctx.fillStyle = this.properties["color"]; - var v = this.properties["value"]; - - if (this.properties["glowSize"]) { - ctx.shadowColor = this.properties.color; - ctx.shadowOffsetX = 0; - ctx.shadowOffsetY = 0; - ctx.shadowBlur = this.properties["glowSize"]; - } else { - ctx.shadowColor = "transparent"; - } - - var fontsize = this.properties["fontsize"]; - - ctx.textAlign = this.properties["align"]; - ctx.font = fontsize.toString() + "px " + this.properties["font"]; - this.str = - typeof v == "number" ? v.toFixed(this.properties["decimals"]) : v; - - if (typeof this.str == "string") { - var lines = this.str.split("\\n"); - for (var i=0; i < lines.length; i++) { - ctx.fillText( - lines[i], - this.properties["align"] == "left" ? 15 : this.size[0] - 15, - fontsize * -0.15 + fontsize * (parseInt(i) + 1) - ); - } - } - - ctx.shadowColor = "transparent"; - this.last_ctx = ctx; - ctx.textAlign = "left"; - }; - - WidgetText.prototype.onExecute = function() { - var v = this.getInputData(0); - if (v != null) { - this.properties["value"] = v; - } - //this.setDirtyCanvas(true); - }; - - WidgetText.prototype.resize = function() { - if (!this.last_ctx) { - return; - } - - var lines = this.str.split("\\n"); - this.last_ctx.font = - this.properties["fontsize"] + "px " + this.properties["font"]; - var max = 0; - for (var i=0; i < lines.length; i++) { - var w = this.last_ctx.measureText(lines[i]).width; - if (max < w) { - max = w; - } - } - this.size[0] = max + 20; - this.size[1] = 4 + lines.length * this.properties["fontsize"]; - - this.setDirtyCanvas(true); - }; - - WidgetText.prototype.onPropertyChanged = function(name, value) { - this.properties[name] = value; - this.str = typeof value == "number" ? value.toFixed(3) : value; - //this.resize(); - return true; - }; - - LiteGraph.registerNodeType("widget/text", WidgetText); - - function WidgetPanel() { - this.size = [200, 100]; - this.properties = { - borderColor: "#ffffff", - bgcolorTop: "#f0f0f0", - bgcolorBottom: "#e0e0e0", - shadowSize: 2, - borderRadius: 3 - }; - } - - WidgetPanel.title = "Panel"; - WidgetPanel.desc = "Non interactive panel"; - WidgetPanel.widgets = [{ name: "update", text: "Update", type: "button" }]; - - WidgetPanel.prototype.createGradient = function(ctx) { - if ( - this.properties["bgcolorTop"] == "" || - this.properties["bgcolorBottom"] == "" - ) { - this.lineargradient = 0; - return; - } - - this.lineargradient = ctx.createLinearGradient(0, 0, 0, this.size[1]); - this.lineargradient.addColorStop(0, this.properties["bgcolorTop"]); - this.lineargradient.addColorStop(1, this.properties["bgcolorBottom"]); - }; - - WidgetPanel.prototype.onDrawForeground = function(ctx) { - if (this.flags.collapsed) { - return; - } - - if (this.lineargradient == null) { - this.createGradient(ctx); - } - - if (!this.lineargradient) { - return; - } - - ctx.lineWidth = 1; - ctx.strokeStyle = this.properties["borderColor"]; - //ctx.fillStyle = "#ebebeb"; - ctx.fillStyle = this.lineargradient; - - if (this.properties["shadowSize"]) { - ctx.shadowColor = "#000"; - ctx.shadowOffsetX = 0; - ctx.shadowOffsetY = 0; - ctx.shadowBlur = this.properties["shadowSize"]; - } else { - ctx.shadowColor = "transparent"; - } - - ctx.roundRect( - 0, - 0, - this.size[0] - 1, - this.size[1] - 1, - this.properties["shadowSize"] - ); - ctx.fill(); - ctx.shadowColor = "transparent"; - ctx.stroke(); - }; - - 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.CENTER = 0; - GamepadInput.LEFT = 1; - GamepadInput.RIGHT = 2; - GamepadInput.UP = 4; - GamepadInput.DOWN = 8; - - 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 "hat_left": - v = gamepad.xbox.hatmap & GamepadInput.LEFT; - break; - case "hat_right": - v = gamepad.xbox.hatmap & GamepadInput.RIGHT; - break; - case "hat_up": - v = gamepad.xbox.hatmap & GamepadInput.UP; - break; - case "hat_down": - v = gamepad.xbox.hatmap & GamepadInput.DOWN; - break; - case "hat": - v = gamepad.xbox.hatmap; - 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.mapping = {a:0,b:1,x:2,y:3,lb:4,rb:5,lt:6,rt:7,back:8,start:9,ls:10,rs:11 }; - GamepadInput.mapping_array = ["a","b","x","y","lb","rb","lt","rt","back","start","ls","rs"]; - - 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]) { - continue; - } - gamepad = gamepads[i]; - - //xbox controller mapping - var xbox = this.xbox_mapping; - if (!xbox) { - xbox = this.xbox_mapping = { - axes: [], - buttons: {}, - hat: "", - hatmap: GamepadInput.CENTER - }; - } - - 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; - xbox.hat = ""; - xbox.hatmap = GamepadInput.CENTER; - - for (var j = 0; j < gamepad.buttons.length; j++) { - this._current_buttons[j] = gamepad.buttons[j].pressed; - - if(j < 12) - { - xbox.buttons[ GamepadInput.mapping_array[j] ] = gamepad.buttons[j].pressed; - if(gamepad.buttons[j].was_pressed) - this.trigger( GamepadInput.mapping_array[j] + "_button_event" ); - } - else //mapping of XBOX - switch ( j ) //I use a switch to ensure that a player with another gamepad could play - { - case 12: - if (gamepad.buttons[j].pressed) { - xbox.hat += "up"; - xbox.hatmap |= GamepadInput.UP; - } - break; - case 13: - if (gamepad.buttons[j].pressed) { - xbox.hat += "down"; - xbox.hatmap |= GamepadInput.DOWN; - } - break; - case 14: - if (gamepad.buttons[j].pressed) { - xbox.hat += "left"; - xbox.hatmap |= GamepadInput.LEFT; - } - break; - case 15: - if (gamepad.buttons[j].pressed) { - xbox.hat += "right"; - xbox.hatmap |= GamepadInput.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_button", "number"], - ["back_button", "number"], - ["a_button_event", LiteGraph.EVENT ], - ["b_button_event", LiteGraph.EVENT ], - ["x_button_event", LiteGraph.EVENT ], - ["y_button_event", LiteGraph.EVENT ], - ["lb_button_event", LiteGraph.EVENT ], - ["rb_button_event", LiteGraph.EVENT ], - ["ls_button_event", LiteGraph.EVENT ], - ["rs_button_event", LiteGraph.EVENT ], - ["start_button_event", LiteGraph.EVENT ], - ["back_button_event", LiteGraph.EVENT ], - ["hat_left", "number"], - ["hat_right", "number"], - ["hat_up", "number"], - ["hat_down", "number"], - ["hat", "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 = [80, 30]; - } - - 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 = [80, 30]; - } - - 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 ToNumber() { - this.addInput("in"); - this.addOutput("out"); - } - - ToNumber.title = "to Number"; - ToNumber.desc = "Cast to number"; - - ToNumber.prototype.onExecute = function() { - var v = this.getInputData(0); - this.setOutputData(0, Number(v)); - }; - - LiteGraph.registerNodeType("math/to_number", ToNumber); - - 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, 30]; - } - - 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 = [80, 30]; - } - - 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 - this.outputs[0].label = (this._last_v || 0).toFixed(3); - }; - - MathRand.prototype.onGetInputs = function() { - return [["min", "number"], ["max", "number"]]; - }; - - LiteGraph.registerNodeType("math/rand", MathRand); - - //basic continuous noise - function MathNoise() { - this.addInput("in", "number"); - this.addOutput("out", "number"); - this.addProperty("min", 0); - this.addProperty("max", 1); - this.addProperty("smooth", true); - this.size = [90, 30]; - } - - MathNoise.title = "Noise"; - MathNoise.desc = "Random number with temporal continuity"; - MathNoise.data = null; - - MathNoise.getValue = function(f, smooth) { - if (!MathNoise.data) { - MathNoise.data = new Float32Array(1024); - for (var i = 0; i < MathNoise.data.length; ++i) { - MathNoise.data[i] = Math.random(); - } - } - f = f % 1024; - if (f < 0) { - f += 1024; - } - var f_min = Math.floor(f); - var f = f - f_min; - var r1 = MathNoise.data[f_min]; - var r2 = MathNoise.data[f_min == 1023 ? 0 : f_min + 1]; - if (smooth) { - f = f * f * f * (f * (f * 6.0 - 15.0) + 10.0); - } - return r1 * (1 - f) + r2 * f; - }; - - MathNoise.prototype.onExecute = function() { - var f = this.getInputData(0) || 0; - var r = MathNoise.getValue(f, this.properties.smooth); - var min = this.properties.min; - var max = this.properties.max; - this._last_v = r * (max - min) + min; - this.setOutputData(0, this._last_v); - }; - - MathNoise.prototype.onDrawBackground = function(ctx) { - //show the current value - this.outputs[0].label = (this._last_v || 0).toFixed(3); - }; - - LiteGraph.registerNodeType("math/noise", MathNoise); - - //generates spikes every random time - function MathSpikes() { - this.addOutput("out", "number"); - this.addProperty("min_time", 1); - this.addProperty("max_time", 2); - this.addProperty("duration", 0.2); - this.size = [90, 30]; - this._remaining_time = 0; - this._blink_time = 0; - } - - MathSpikes.title = "Spikes"; - MathSpikes.desc = "spike every random time"; - - MathSpikes.prototype.onExecute = function() { - var dt = this.graph.elapsed_time; //in secs - - this._remaining_time -= dt; - this._blink_time -= dt; - - var v = 0; - if (this._blink_time > 0) { - var f = this._blink_time / this.properties.duration; - v = 1 / (Math.pow(f * 8 - 4, 4) + 1); - } - - if (this._remaining_time < 0) { - this._remaining_time = - Math.random() * - (this.properties.max_time - this.properties.min_time) + - this.properties.min_time; - this._blink_time = this.properties.duration; - this.boxcolor = "#FFF"; - } else { - this.boxcolor = "#000"; - } - this.setOutputData(0, v); - }; - - LiteGraph.registerNodeType("math/spikes", MathSpikes); - - //Math clamp - function MathClamp() { - this.addInput("in", "number"); - this.addOutput("out", "number"); - this.size = [80, 30]; - 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 = [80, 30]; - } - - 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 = [80, 30]; - } - - 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 = [80, 30]; - } - - 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 = [80, 30]; - 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 = [80, 30]; - 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); - - //Gate - function Gate() { - this.addInput("v","boolean"); - this.addInput("A"); - this.addInput("B"); - this.addOutput("out"); - } - - Gate.title = "Gate"; - Gate.desc = "if v is true, then outputs A, otherwise B"; - - Gate.prototype.onExecute = function() { - var v = this.getInputData(0); - this.setOutputData(0, this.getInputData( v ? 1 : 2 )); - }; - - LiteGraph.registerNodeType("math/gate", Gate); - - - //Math Average - function MathAverageFilter() { - this.addInput("in", "number"); - this.addOutput("out", "number"); - this.size = [80, 30]; - 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 = [80, 30]; - 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 = ["+", "-", "*", "/", "%", "^", "max", "min"]; - - MathOperation.title = "Operation"; - MathOperation.desc = "Easy math operators"; - MathOperation["@OP"] = { - type: "enum", - title: "operation", - values: MathOperation.values - }; - MathOperation.size = [100, 60]; - - MathOperation.prototype.getTitle = function() { - if(this.properties.OP == "max" || this.properties.OP == "min") - return this.properties.OP + "(A,B)"; - 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; - case "max": - result = Math.max(A, B); - break; - case "min": - result = Math.min(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 = "#666"; - ctx.textAlign = "center"; - ctx.fillText( - this.properties.OP, - this.size[0] * 0.5, - (this.size[1] + LiteGraph.NODE_TITLE_HEIGHT) * 0.5 - ); - ctx.textAlign = "left"; - }; - - LiteGraph.registerNodeType("math/operation", MathOperation); - - LiteGraph.registerSearchboxExtra("math/operation", "MAX", { - properties: {OP:"max"}, - title: "MAX()" - }); - - LiteGraph.registerSearchboxExtra("math/operation", "MIN", { - properties: {OP:"min"}, - title: "MIN()" - }); - - //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; - } - var value; - 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); - - LiteGraph.registerSearchboxExtra("math/compare", "==", { - outputs: [["A==B", "boolean"]], - title: "A==B" - }); - LiteGraph.registerSearchboxExtra("math/compare", "!=", { - outputs: [["A!=B", "boolean"]], - title: "A!=B" - }); - LiteGraph.registerSearchboxExtra("math/compare", ">", { - outputs: [["A>B", "boolean"]], - title: "A>B" - }); - LiteGraph.registerSearchboxExtra("math/compare", "<", { - outputs: [["A=", { - outputs: [["A>=B", "boolean"]], - title: "A>=B" - }); - LiteGraph.registerSearchboxExtra("math/compare", "<=", { - outputs: [["A<=B", "boolean"]], - title: "A<=B" - }); - - function MathCondition() { - this.addInput("A", "number"); - this.addInput("B", "number"); - this.addOutput("true", "boolean"); - this.addOutput("false", "boolean"); - this.addProperty("A", 1); - this.addProperty("B", 1); - this.addProperty("OP", ">", "enum", { values: MathCondition.values }); - - this.size = [80, 60]; - } - - MathCondition.values = [">", "<", "==", "!=", "<=", ">=", "||", "&&" ]; - MathCondition["@OP"] = { - type: "enum", - title: "operation", - values: MathCondition.values - }; - - MathCondition.title = "Condition"; - MathCondition.desc = "evaluates condition between A and B"; - - MathCondition.prototype.getTitle = function() { - return "A " + this.properties.OP + " 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 < B; - break; - case "==": - result = A == B; - break; - case "!=": - result = A != B; - break; - case "<=": - result = A <= B; - break; - case ">=": - result = A >= B; - break; - case "||": - result = A || B; - break; - case "&&": - result = A && B; - break; - } - - this.setOutputData(0, result); - this.setOutputData(1, !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]; - var value; - 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); - - LiteGraph.registerSearchboxExtra("math/trigonometry", "SIN()", { - outputs: [["sin", "number"]], - title: "SIN()" - }); - LiteGraph.registerSearchboxExtra("math/trigonometry", "COS()", { - outputs: [["cos", "number"]], - title: "COS()" - }); - LiteGraph.registerSearchboxExtra("math/trigonometry", "TAN()", { - outputs: [["tan", "number"]], - title: "TAN()" - }); - - //math library for safe math operations without eval - function MathFormula() { - this.addInput("x", "number"); - this.addInput("y", "number"); - this.addOutput("", "number"); - this.properties = { x: 1.0, y: 1.0, formula: "x+y" }; - this.code_widget = this.addWidget( - "text", - "F(x,y)", - this.properties.formula, - function(v, canvas, node) { - node.properties.formula = v; - } - ); - this.addWidget("toggle", "allow", LiteGraph.allow_scripts, function(v) { - LiteGraph.allow_scripts = v; - }); - this._func = null; - } - - MathFormula.title = "Formula"; - MathFormula.desc = "Compute formula"; - MathFormula.size = [160, 100]; - - MathAverageFilter.prototype.onPropertyChanged = function(name, value) { - if (name == "formula") { - this.code_widget.value = value; - } - }; - - MathFormula.prototype.onExecute = function() { - if (!LiteGraph.allow_scripts) { - return; - } - - 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; - try { - if (!this._func || this._func_code != this.properties.formula) { - this._func = new Function( - "x", - "y", - "TIME", - "return " + this.properties.formula - ); - this._func_code = this.properties.formula; - } - value = this._func(x, y, this.graph.globaltime); - this.boxcolor = null; - } catch (err) { - this.boxcolor = "red"; - } - this.setOutputData(0, value); - }; - - MathFormula.prototype.getTitle = function() { - return this._func_code || "Formula"; - }; - - MathFormula.prototype.onDrawBackground = function() { - var f = this.properties["formula"]; - if (this.outputs && this.outputs.length) { - this.outputs[0].label = f; - } - }; - - LiteGraph.registerNodeType("math/formula", MathFormula); - - function Math3DVec2ToXY() { - this.addInput("vec2", "vec2"); - this.addOutput("x", "number"); - this.addOutput("y", "number"); - } - - Math3DVec2ToXY.title = "Vec2->XY"; - Math3DVec2ToXY.desc = "vector 2 to components"; - - Math3DVec2ToXY.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-xy", Math3DVec2ToXY); - - function Math3DXYToVec2() { - this.addInputs([["x", "number"], ["y", "number"]]); - this.addOutput("vec2", "vec2"); - this.properties = { x: 0, y: 0 }; - this._data = new Float32Array(2); - } - - Math3DXYToVec2.title = "XY->Vec2"; - Math3DXYToVec2.desc = "components to vector2"; - - Math3DXYToVec2.prototype.onExecute = function() { - var x = this.getInputData(0); - if (x == null) { - x = this.properties.x; - } - var y = this.getInputData(1); - if (y == null) { - y = this.properties.y; - } - - var data = this._data; - data[0] = x; - data[1] = y; - - this.setOutputData(0, data); - }; - - LiteGraph.registerNodeType("math3d/xy-to-vec2", Math3DXYToVec2); - - function Math3DVec3ToXYZ() { - this.addInput("vec3", "vec3"); - this.addOutput("x", "number"); - this.addOutput("y", "number"); - this.addOutput("z", "number"); - } - - Math3DVec3ToXYZ.title = "Vec3->XYZ"; - Math3DVec3ToXYZ.desc = "vector 3 to components"; - - Math3DVec3ToXYZ.prototype.onExecute = function() { - var v = this.getInputData(0); - if (v == null) { - return; - } - - this.setOutputData(0, v[0]); - this.setOutputData(1, v[1]); - this.setOutputData(2, v[2]); - }; - - LiteGraph.registerNodeType("math3d/vec3-to-xyz", Math3DVec3ToXYZ); - - function Math3DXYZToVec3() { - this.addInputs([["x", "number"], ["y", "number"], ["z", "number"]]); - this.addOutput("vec3", "vec3"); - this.properties = { x: 0, y: 0, z: 0 }; - this._data = new Float32Array(3); - } - - Math3DXYZToVec3.title = "XYZ->Vec3"; - Math3DXYZToVec3.desc = "components to vector3"; - - Math3DXYZToVec3.prototype.onExecute = function() { - var x = this.getInputData(0); - if (x == null) { - x = this.properties.x; - } - var y = this.getInputData(1); - if (y == null) { - y = this.properties.y; - } - var z = this.getInputData(2); - if (z == null) { - z = this.properties.z; - } - - var data = this._data; - data[0] = x; - data[1] = y; - data[2] = z; - - this.setOutputData(0, data); - }; - - LiteGraph.registerNodeType("math3d/xyz-to-vec3", Math3DXYZToVec3); - - function Math3DVec4ToXYZW() { - this.addInput("vec4", "vec4"); - this.addOutput("x", "number"); - this.addOutput("y", "number"); - this.addOutput("z", "number"); - this.addOutput("w", "number"); - } - - Math3DVec4ToXYZW.title = "Vec4->XYZW"; - Math3DVec4ToXYZW.desc = "vector 4 to components"; - - Math3DVec4ToXYZW.prototype.onExecute = function() { - var v = this.getInputData(0); - if (v == null) { - return; - } - - this.setOutputData(0, v[0]); - this.setOutputData(1, v[1]); - this.setOutputData(2, v[2]); - this.setOutputData(3, v[3]); - }; - - LiteGraph.registerNodeType("math3d/vec4-to-xyzw", Math3DVec4ToXYZW); - - function Math3DXYZWToVec4() { - this.addInputs([ - ["x", "number"], - ["y", "number"], - ["z", "number"], - ["w", "number"] - ]); - this.addOutput("vec4", "vec4"); - this.properties = { x: 0, y: 0, z: 0, w: 0 }; - this._data = new Float32Array(4); - } - - Math3DXYZWToVec4.title = "XYZW->Vec4"; - Math3DXYZWToVec4.desc = "components to vector4"; - - Math3DXYZWToVec4.prototype.onExecute = function() { - var x = this.getInputData(0); - if (x == null) { - x = this.properties.x; - } - var y = this.getInputData(1); - if (y == null) { - y = this.properties.y; - } - var z = this.getInputData(2); - if (z == null) { - z = this.properties.z; - } - var w = this.getInputData(3); - if (w == null) { - w = this.properties.w; - } - - var data = this._data; - data[0] = x; - data[1] = y; - data[2] = z; - data[3] = w; - - this.setOutputData(0, data); - }; - - LiteGraph.registerNodeType("math3d/xyzw-to-vec4", Math3DXYZWToVec4); - - //if glMatrix is installed... - if (global.glMatrix) { - function Math3DQuaternion() { - this.addOutput("quat", "quat"); - this.properties = { x: 0, y: 0, z: 0, w: 1 }; - this._value = quat.create(); - } - - Math3DQuaternion.title = "Quaternion"; - Math3DQuaternion.desc = "quaternion"; - - Math3DQuaternion.prototype.onExecute = function() { - this._value[0] = this.properties.x; - this._value[1] = this.properties.y; - this._value[2] = this.properties.z; - this._value[3] = this.properties.w; - this.setOutputData(0, this._value); - }; - - LiteGraph.registerNodeType("math3d/quaternion", Math3DQuaternion); - - function Math3DRotation() { - this.addInputs([["degrees", "number"], ["axis", "vec3"]]); - this.addOutput("quat", "quat"); - this.properties = { angle: 90.0, axis: vec3.fromValues(0, 1, 0) }; - - this._value = quat.create(); - } - - Math3DRotation.title = "Rotation"; - Math3DRotation.desc = "quaternion rotation"; - - Math3DRotation.prototype.onExecute = function() { - var angle = this.getInputData(0); - if (angle == null) { - angle = this.properties.angle; - } - var axis = this.getInputData(1); - if (axis == null) { - axis = this.properties.axis; - } - - var R = quat.setAxisAngle(this._value, axis, angle * 0.0174532925); - this.setOutputData(0, R); - }; - - LiteGraph.registerNodeType("math3d/rotation", Math3DRotation); - - //Math3D rotate vec3 - function Math3DRotateVec3() { - this.addInputs([["vec3", "vec3"], ["quat", "quat"]]); - this.addOutput("result", "vec3"); - this.properties = { vec: [0, 0, 1] }; - } - - Math3DRotateVec3.title = "Rot. Vec3"; - Math3DRotateVec3.desc = "rotate a point"; - - Math3DRotateVec3.prototype.onExecute = function() { - var vec = this.getInputData(0); - if (vec == null) { - vec = this.properties.vec; - } - var quat = this.getInputData(1); - if (quat == null) { - this.setOutputData(vec); - } else { - this.setOutputData( - 0, - vec3.transformQuat(vec3.create(), vec, quat) - ); - } - }; - - LiteGraph.registerNodeType("math3d/rotate_vec3", Math3DRotateVec3); - - function Math3DMultQuat() { - this.addInputs([["A", "quat"], ["B", "quat"]]); - this.addOutput("A*B", "quat"); - - this._value = quat.create(); - } - - Math3DMultQuat.title = "Mult. Quat"; - Math3DMultQuat.desc = "rotate quaternion"; - - Math3DMultQuat.prototype.onExecute = function() { - var A = this.getInputData(0); - if (A == null) { - return; - } - var B = this.getInputData(1); - if (B == null) { - return; - } - - var R = quat.multiply(this._value, A, B); - this.setOutputData(0, R); - }; - - LiteGraph.registerNodeType("math3d/mult-quat", Math3DMultQuat); - - function Math3DQuatSlerp() { - this.addInputs([ - ["A", "quat"], - ["B", "quat"], - ["factor", "number"] - ]); - this.addOutput("slerp", "quat"); - this.addProperty("factor", 0.5); - - this._value = quat.create(); - } - - Math3DQuatSlerp.title = "Quat Slerp"; - Math3DQuatSlerp.desc = "quaternion spherical interpolation"; - - Math3DQuatSlerp.prototype.onExecute = function() { - var A = this.getInputData(0); - if (A == null) { - return; - } - var B = this.getInputData(1); - if (B == null) { - return; - } - var factor = this.properties.factor; - if (this.getInputData(2) != null) { - factor = this.getInputData(2); - } - - var R = quat.slerp(this._value, A, B, factor); - this.setOutputData(0, R); - }; - - LiteGraph.registerNodeType("math3d/quat-slerp", Math3DQuatSlerp); - } //glMatrix -})(this); - -(function(global) { - var LiteGraph = global.LiteGraph; - - function Selector() { - this.addInput("sel", "number"); - this.addInput("A"); - this.addInput("B"); - this.addInput("C"); - this.addInput("D"); - this.addOutput("out"); - - this.selected = 0; - } - - Selector.title = "Selector"; - Selector.desc = "selects an output"; - - Selector.prototype.onDrawBackground = function(ctx) { - if (this.flags.collapsed) { - return; - } - ctx.fillStyle = "#AFB"; - var y = (this.selected + 1) * LiteGraph.NODE_SLOT_HEIGHT + 6; - ctx.beginPath(); - ctx.moveTo(50, y); - ctx.lineTo(50, y + LiteGraph.NODE_SLOT_HEIGHT); - ctx.lineTo(34, y + LiteGraph.NODE_SLOT_HEIGHT * 0.5); - ctx.fill(); - }; - - Selector.prototype.onExecute = function() { - var sel = this.getInputData(0); - if (sel == null || sel.constructor !== Number) - sel = 0; - this.selected = sel = Math.round(sel) % (this.inputs.length - 1); - var v = this.getInputData(sel + 1); - if (v !== undefined) { - this.setOutputData(0, v); - } - }; - - Selector.prototype.onGetInputs = function() { - return [["E", 0], ["F", 0], ["G", 0], ["H", 0]]; - }; - - LiteGraph.registerNodeType("logic/selector", Selector); - - function Sequence() { - this.properties = { - sequence: "A,B,C" - }; - this.addInput("index", "number"); - this.addInput("seq"); - this.addOutput("out"); - - this.index = 0; - this.values = this.properties.sequence.split(","); - } - - Sequence.title = "Sequence"; - Sequence.desc = "select one element from a sequence from a string"; - - Sequence.prototype.onPropertyChanged = function(name, value) { - if (name == "sequence") { - this.values = value.split(","); - } - }; - - Sequence.prototype.onExecute = function() { - var seq = this.getInputData(1); - if (seq && seq != this.current_sequence) { - this.values = seq.split(","); - this.current_sequence = seq; - } - var index = this.getInputData(0); - if (index == null) { - index = 0; - } - this.index = index = Math.round(index) % this.values.length; - - this.setOutputData(0, this.values[index]); - }; - - LiteGraph.registerNodeType("logic/sequence", Sequence); -})(this); - -(function(global) { - var LiteGraph = global.LiteGraph; - - //Works with Litegl.js to create WebGL nodes - global.LGraphTexture = null; - - if (typeof GL == "undefined") - return; - - LGraphCanvas.link_type_colors["Texture"] = "#987"; - - function LGraphTexture() { - this.addOutput("tex", "Texture"); - this.addOutput("name", "string"); - 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.getWhiteTexture = function() { - if (this._white_texture) { - return this._white_texture; - } - var texture = (this._white_texture = GL.Texture.fromMemory( - 1, - 1, - [255, 255, 255, 255], - { format: gl.RGBA, wrap: gl.REPEAT, filter: gl.NEAREST } - )); - return texture; - }; - - 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) { - this.setOutputData( 0, null ); - this.setOutputData( 1, "" ); - 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 ); - this.setOutputData( 1, tex.fullpath || tex.filename ); - - for (var i = 2; 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) { - if(this.properties.name) - 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"] - ]; - }; - - //used to replace shader code - LGraphTexture.replaceCode = function( code, context ) - { - return code.replace(/\{\{[a-zA-Z0-9_]*\}\}/g, function(v){ - v = v.replace( /[\{\}]/g, "" ); - return context[v] || ""; - }); - } - - 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("tex", "Texture"); - this.addOutput("name", "string"); - this.properties = { name: "", generate_mipmaps: false }; - } - - LGraphTextureSave.title = "Save"; - LGraphTextureSave.desc = "Save a texture in the repository"; - - LGraphTextureSave.prototype.getPreviewTexture = function() - { - return this._texture; - } - - LGraphTextureSave.prototype.onExecute = function() { - var tex = this.getInputData(0); - if (!tex) { - return; - } - - if (this.properties.generate_mipmaps) { - tex.bind(0); - tex.setParameter( gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR ); - gl.generateMipmap(tex.texture_type); - tex.unbind(0); - } - - 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._texture = tex; - this.setOutputData(0, tex); - this.setOutputData(1, this.properties.name); - }; - - 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

For multiline you must type: result = ...

"; - - this.properties = { - value: 1, - pixelcode: "color + colorB * value", - uvcode: "", - precision: LGraphTexture.DEFAULT - }; - - this.has_error = false; - } - - LGraphTextureOperation.widgets_info = { - uvcode: { widget: "code" }, - pixelcode: { widget: "code" }, - precision: { widget: "combo", values: LGraphTexture.MODE_VALUES } - }; - - LGraphTextureOperation.title = "Operation"; - LGraphTextureOperation.desc = "Texture shader operation"; - - LGraphTextureOperation.presets = {}; - - 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.onPropertyChanged = function() - { - this.has_error = false; - } - - 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; - } - - if(!texB) - texB = GL.Texture.getWhiteTexture(); - - 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 ( !this.has_error && (!shader || this._shader_code != uvcode + "|" + pixelcode) ) { - - var final_pixel_code = LGraphTexture.replaceCode( LGraphTextureOperation.pixel_shader, { UV_CODE:uvcode, PIXEL_CODE:pixelcode }); - - try { - shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, final_pixel_code ); - this.boxcolor = "#00FF00"; - } catch (err) { - //console.log("Error compiling shader: ", err, final_pixel_code ); - GL.Shader.dumpErrorToConsole(err,Shader.SCREEN_VERTEX_SHADER, final_pixel_code); - this.boxcolor = "#FF0000"; - this.has_error = true; - return; - } - this._shader = shader; - this._shader_code = uvcode + "|" + pixelcode; - } - - if(!this._shader) - return; - - 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\ - "; - - LGraphTextureOperation.registerPreset = function ( name, code ) - { - LGraphTextureOperation.presets[name] = code; - } - - LGraphTextureOperation.registerPreset("",""); - LGraphTextureOperation.registerPreset("bypass","color"); - LGraphTextureOperation.registerPreset("add","color + colorB * value"); - LGraphTextureOperation.registerPreset("substract","(color - colorB) * value"); - LGraphTextureOperation.registerPreset("mate","mix( color, colorB, color4B.a * value)"); - LGraphTextureOperation.registerPreset("invert","vec3(1.0) - color"); - LGraphTextureOperation.registerPreset("multiply","color * colorB * value"); - LGraphTextureOperation.registerPreset("divide","(color / colorB) / value"); - LGraphTextureOperation.registerPreset("difference","abs(color - colorB) * value"); - LGraphTextureOperation.registerPreset("max","max(color, colorB) * value"); - LGraphTextureOperation.registerPreset("min","min(color, colorB) * value"); - LGraphTextureOperation.registerPreset("displace","texture2D(u_texture, uv + (colorB.xy - vec2(0.5)) * value).xyz"); - LGraphTextureOperation.registerPreset("grayscale","vec3(color.x + color.y + color.z) * value / 3.0"); - LGraphTextureOperation.registerPreset("saturation","mix( vec3(color.x + color.y + color.z) / 3.0, color, value )"); - LGraphTextureOperation.registerPreset("threshold","vec3(color.x > colorB.x * value ? 1.0 : 0.0,color.y > colorB.y * value ? 1.0 : 0.0,color.z > colorB.z * value ? 1.0 : 0.0)"); - - //webglstudio stuff... - LGraphTextureOperation.prototype.onInspect = function(widgets) - { - var that = this; - widgets.addCombo("Presets","",{ values: Object.keys(LGraphTextureOperation.presets), callback: function(v){ - var code = LGraphTextureOperation.presets[v]; - if(!code) - return; - that.setProperty("pixelcode",code); - that.title = v; - widgets.refresh(); - }}); - } - - LiteGraph.registerNodeType("texture/operation", LGraphTextureOperation); - - //**************************************************** - - function LGraphTextureShader() { - this.addOutput("out", "Texture"); - this.properties = { - code: "", - u_value: 1, - u_color: [1,1,1,1], - width: 512, - height: 512, - precision: LGraphTexture.DEFAULT - }; - - this.properties.code = - "//time: time in seconds\n//texSize: vec2 with res\nuniform float u_value;\nuniform vec4 u_color;\n\nvoid main() {\n vec2 uv = v_coord;\n vec3 color = vec3(0.0);\n //your code here\n color.xy=uv;\n\ngl_FragColor = vec4(color, 1.0);\n}\n"; - this._uniforms = { u_value: 1, u_color: vec4.create(), 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 - if(this.inputs) - 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(); - uniforms.u_value = this.properties.u_value; - uniforms.u_color.set( this.properties.u_color ); - - 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, - scale: [1,1], - offset: [0,0], - precision: LGraphTexture.DEFAULT - }; - - this._uniforms = { - u_texture: 0, - u_textureB: 1, - u_factor: 1, - u_scale: vec2.create(), - u_offset: vec2.create() - }; - } - - 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); - } - var uniforms = this._uniforms; - uniforms.u_factor = factor; - uniforms.u_scale.set( this.properties.scale ); - uniforms.u_offset.set( this.properties.offset ); - - 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( uniforms ) - .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\ - uniform vec2 u_scale;\n\ - uniform vec2 u_offset;\n\ - \n\ - void main() {\n\ - vec2 uv = v_coord;\n\ - uv += ( texture2D(u_textureB, uv).rg - vec2(0.5)) * u_factor * u_scale + u_offset;\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, - viewport: [0,0,1,1] - }; - this.size[0] = 130; - } - - LGraphTextureToViewport.title = "to Viewport"; - LGraphTextureToViewport.desc = "Texture to viewport"; - - LGraphTextureToViewport._prev_viewport = new Float32Array(4); - 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 - ); - - var old_viewport = LGraphTextureToViewport._prev_viewport; - old_viewport.set( gl.viewport_data ); - var new_view = this.properties.viewport; - gl.viewport( old_viewport[0] + old_viewport[2] * new_view[0], old_viewport[1] + old_viewport[3] * new_view[1], old_viewport[2] * new_view[2], old_viewport[3] * new_view[3] ); - var viewport = gl.getViewport(); //gl.getParameter(gl.VIEWPORT); - - if (this.properties.antialiasing) { - if (!LGraphTextureToViewport._shader) { - LGraphTextureToViewport._shader = new GL.Shader( - GL.Shader.SCREEN_VERTEX_SHADER, - LGraphTextureToViewport.aa_pixel_shader - ); - } - - 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(); - } - } - - gl.viewport( old_viewport[0], old_viewport[1], old_viewport[2], old_viewport[3] ); - }; - - 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: 0 }, - 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; - } - - if (this.properties.iterations < 1) { - this.setOutputData(0, tex); - 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 Average ***************************************** - function LGraphTextureAverage() { - this.addInput("Texture", "Texture"); - this.addOutput("tex", "Texture"); - this.addOutput("avg", "vec4"); - this.addOutput("lum", "number"); - this.properties = { - use_previous_frame: true, //to avoid stalls - high_quality: false //to use as much pixels as possible - }; - - this._uniforms = { - u_texture: 0, - u_mipmap_offset: 0 - }; - 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.\n If high_quality is true, then it generates the mipmaps first and reads from the lower one."; - - LGraphTextureAverage.prototype.onExecute = function() { - if (!this.properties.use_previous_frame) { - this.updateAverage(); - } - - var v = this._luminance; - this.setOutputData(0, this._temp_texture); - this.setOutputData(1, v); - this.setOutputData(2, (v[0] + v[1] + v[2]) / 3); - }; - - //executed before rendering the frame - LGraphTextureAverage.prototype.onPreRenderExecute = function() { - this.updateAverage(); - }; - - LGraphTextureAverage.prototype.updateAverage = 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 256 random numbers and stores them in two mat4 - var samples = new Float32Array(16); - for (var i = 0; i < samples.length; ++i) { - samples[i] = Math.random(); //poorly distributed samples - } - //upload only once - 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 - }); - } - - this._uniforms.u_mipmap_offset = 0; - - if(this.properties.high_quality) - { - if( !this._temp_pot2_texture || this._temp_pot2_texture.type != type ) - this._temp_pot2_texture = new GL.Texture(512, 512, { - type: type, - format: gl.RGBA, - minFilter: gl.LINEAR_MIPMAP_LINEAR, - magFilter: gl.LINEAR - }); - - tex.copyTo( this._temp_pot2_texture ); - tex = this._temp_pot2_texture; - tex.bind(0); - gl.generateMipmap(GL.TEXTURE_2D); - this._uniforms.u_mipmap_offset = 9; - } - - var shader = LGraphTextureAverage._shader; - var uniforms = this._uniforms; - uniforms.u_mipmap_offset = this.properties.mipmap_offset; - gl.disable(gl.DEPTH_TEST); - gl.disable(gl.BLEND); - this._temp_texture.drawTo(function() { - tex.toViewport(shader, uniforms); - }); - - 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 - ) { - //no half floats possible, hard to read back unless copyed to a FLOAT texture, so temp_texture is always forced to FLOAT - } - } - } - }; - - 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\ - //random average\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); - - - - // Computes operation between pixels (max, min) ***************************************** - function LGraphTextureMinMax() { - this.addInput("Texture", "Texture"); - this.addOutput("min_t", "Texture"); - this.addOutput("max_t", "Texture"); - this.addOutput("min", "vec4"); - this.addOutput("max", "vec4"); - this.properties = { - mode: "max", - use_previous_frame: true //to avoid stalls - }; - - this._uniforms = { - u_texture: 0 - }; - - this._max = new Float32Array(4); - this._min = new Float32Array(4); - - this._textures_chain = []; - } - - LGraphTextureMinMax.widgets_info = { - mode: { widget: "combo", values: ["min","max","avg"] } - }; - - LGraphTextureMinMax.title = "MinMax"; - LGraphTextureMinMax.desc = "Compute the scene min max"; - - LGraphTextureMinMax.prototype.onExecute = function() { - if (!this.properties.use_previous_frame) { - this.update(); - } - - this.setOutputData(0, this._temp_texture); - this.setOutputData(1, this._luminance); - }; - - //executed before rendering the frame - LGraphTextureMinMax.prototype.onPreRenderExecute = function() { - this.update(); - }; - - LGraphTextureMinMax.prototype.update = function() { - var tex = this.getInputData(0); - if (!tex) { - return; - } - - if ( !this.isOutputConnected(0) && !this.isOutputConnected(1) ) { - return; - } //saves work - - if (!LGraphTextureMinMax._shader) { - LGraphTextureMinMax._shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureMinMax.pixel_shader ); - } - - 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; - } - - var size = 512; - - if( !this._textures_chain.length || this._textures_chain[0].type != type ) - { - var index = 0; - while(i) - { - this._textures_chain[i] = new GL.Texture( size, size, { - type: type, - format: gl.RGBA, - filter: gl.NEAREST - }); - size = size >> 2; - i++; - if(size == 1) - break; - } - } - - tex.copyTo( this._textures_chain[0] ); - var prev = this._textures_chain[0]; - for(var i = 1; i <= this._textures_chain.length; ++i) - { - var tex = this._textures_chain[i]; - - prev = tex; - } - - var shader = LGraphTextureMinMax._shader; - var uniforms = this._uniforms; - uniforms.u_mipmap_offset = this.properties.mipmap_offset; - gl.disable(gl.DEPTH_TEST); - gl.disable(gl.BLEND); - this._temp_texture.drawTo(function() { - tex.toViewport(shader, uniforms); - }); - }; - - LGraphTextureMinMax.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\ - //random average\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/clustered_operation", LGraphTextureClusteredOperation); - - - function LGraphTextureTemporalSmooth() { - this.addInput("in", "Texture"); - this.addInput("factor", "Number"); - this.addOutput("out", "Texture"); - this.properties = { factor: 0.5 }; - this._uniforms = { - u_texture: 0, - u_textureB: 1, - u_factor: this.properties.factor - }; - } - - LGraphTextureTemporalSmooth.title = "Smooth"; - LGraphTextureTemporalSmooth.desc = "Smooth texture over time"; - - LGraphTextureTemporalSmooth.prototype.onExecute = function() { - var tex = this.getInputData(0); - if (!tex || !this.isOutputConnected(0)) { - return; - } - - if (!LGraphTextureTemporalSmooth._shader) { - LGraphTextureTemporalSmooth._shader = new GL.Shader( - GL.Shader.SCREEN_VERTEX_SHADER, - LGraphTextureTemporalSmooth.pixel_shader - ); - } - - var temp = this._temp_texture; - if ( - !temp || - temp.type != tex.type || - temp.width != tex.width || - temp.height != tex.height - ) { - var options = { - type: tex.type, - format: gl.RGBA, - filter: gl.NEAREST - }; - this._temp_texture = new GL.Texture(tex.width, tex.height, options ); - this._temp_texture2 = new GL.Texture(tex.width, tex.height, options ); - tex.copyTo(this._temp_texture2); - } - - var tempA = this._temp_texture; - var tempB = this._temp_texture2; - - var shader = LGraphTextureTemporalSmooth._shader; - var uniforms = this._uniforms; - uniforms.u_factor = 1.0 - this.getInputOrProperty("factor"); - - gl.disable(gl.BLEND); - gl.disable(gl.DEPTH_TEST); - tempA.drawTo(function() { - tempB.bind(1); - tex.toViewport(shader, uniforms); - }); - - this.setOutputData(0, tempA); - - //swap - this._temp_texture = tempB; - this._temp_texture2 = tempA; - }; - - LGraphTextureTemporalSmooth.pixel_shader = - "precision highp float;\n\ - precision highp float;\n\ - uniform sampler2D u_texture;\n\ - uniform sampler2D u_textureB;\n\ - uniform float u_factor;\n\ - varying vec2 v_coord;\n\ - \n\ - void main() {\n\ - gl_FragColor = mix( texture2D( u_texture, v_coord ), texture2D( u_textureB, v_coord ), u_factor );\n\ - }\n\ - "; - - LiteGraph.registerNodeType( "texture/temporal_smooth", LGraphTextureTemporalSmooth ); - - - function LGraphTextureLinearAvgSmooth() { - this.addInput("in", "Texture"); - this.addOutput("avg", "Texture"); - this.addOutput("array", "Texture"); - this.properties = { samples: 64, frames_interval: 1 }; - this._uniforms = { - u_texture: 0, - u_textureB: 1, - u_samples: this.properties.samples, - u_isamples: 1/this.properties.samples - }; - this.frame = 0; - } - - LGraphTextureLinearAvgSmooth.title = "Lineal Avg Smooth"; - LGraphTextureLinearAvgSmooth.desc = "Smooth texture linearly over time"; - - LGraphTextureLinearAvgSmooth["@samples"] = { type: "number", min: 1, max: 64, step: 1, precision: 1 }; - - LGraphTextureLinearAvgSmooth.prototype.getPreviewTexture = function() - { - return this._temp_texture2; - } - - LGraphTextureLinearAvgSmooth.prototype.onExecute = function() { - - var tex = this.getInputData(0); - if (!tex || !this.isOutputConnected(0)) { - return; - } - - if (!LGraphTextureLinearAvgSmooth._shader) { - LGraphTextureLinearAvgSmooth._shader_copy = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureLinearAvgSmooth.pixel_shader_copy ); - LGraphTextureLinearAvgSmooth._shader_avg = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureLinearAvgSmooth.pixel_shader_avg ); - } - - var samples = Math.clamp(this.properties.samples,0,64); - var frame = this.frame; - var interval = this.properties.frames_interval; - - if( interval == 0 || frame % interval == 0 ) - { - var temp = this._temp_texture; - if ( !temp || temp.type != tex.type || temp.width != samples ) { - var options = { - type: tex.type, - format: gl.RGBA, - filter: gl.NEAREST - }; - this._temp_texture = new GL.Texture( samples, 1, options ); - this._temp_texture2 = new GL.Texture( samples, 1, options ); - this._temp_texture_out = new GL.Texture( 1, 1, options ); - } - - var tempA = this._temp_texture; - var tempB = this._temp_texture2; - - var shader_copy = LGraphTextureLinearAvgSmooth._shader_copy; - var shader_avg = LGraphTextureLinearAvgSmooth._shader_avg; - var uniforms = this._uniforms; - uniforms.u_samples = samples; - uniforms.u_isamples = 1.0 / samples; - - gl.disable(gl.BLEND); - gl.disable(gl.DEPTH_TEST); - tempA.drawTo(function() { - tempB.bind(1); - tex.toViewport( shader_copy, uniforms ); - }); - - this._temp_texture_out.drawTo(function() { - tempA.toViewport( shader_avg, uniforms ); - }); - - this.setOutputData( 0, this._temp_texture_out ); - - //swap - this._temp_texture = tempB; - this._temp_texture2 = tempA; - } - else - this.setOutputData(0, this._temp_texture_out); - this.setOutputData(1, this._temp_texture2); - this.frame++; - }; - - LGraphTextureLinearAvgSmooth.pixel_shader_copy = - "precision highp float;\n\ - precision highp float;\n\ - uniform sampler2D u_texture;\n\ - uniform sampler2D u_textureB;\n\ - uniform float u_isamples;\n\ - varying vec2 v_coord;\n\ - \n\ - void main() {\n\ - if( v_coord.x <= u_isamples )\n\ - gl_FragColor = texture2D( u_texture, vec2(0.5) );\n\ - else\n\ - gl_FragColor = texture2D( u_textureB, v_coord - vec2(u_isamples,0.0) );\n\ - }\n\ - "; - - LGraphTextureLinearAvgSmooth.pixel_shader_avg = - "precision highp float;\n\ - precision highp float;\n\ - uniform sampler2D u_texture;\n\ - uniform int u_samples;\n\ - uniform float u_isamples;\n\ - varying vec2 v_coord;\n\ - \n\ - void main() {\n\ - vec4 color = vec4(0.0);\n\ - for(int i = 0; i < 64; ++i)\n\ - {\n\ - color += texture2D( u_texture, vec2( float(i)*u_isamples,0.0) );\n\ - if(i == (u_samples - 1))\n\ - break;\n\ - }\n\ - gl_FragColor = color * u_isamples;\n\ - }\n\ - "; - - - LiteGraph.registerNodeType( "texture/linear_avg_smooth", LGraphTextureLinearAvgSmooth ); - - // 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 = { enabled: true, 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.properties.enabled === false) { - 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 = { use_single_channel: true }; - 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 format = this.properties.use_single_channel ? gl.LUMINANCE : gl.RGBA; //not supported by WebGL1 - var format = gl.RGB; - 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].format != format - ) { - this._channels[i] = new GL.Texture( - texA.width, - texA.height, - { - type: texA.type, - format: format, - 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 = { - precision: LGraphTexture.DEFAULT, - R: 1, - G: 1, - B: 1, - A: 1 - }; - this._color = vec4.create(); - this._uniforms = { - u_textureR: 0, - u_textureG: 1, - u_textureB: 2, - u_textureA: 3, - u_color: this._color - }; - } - - LGraphChannelsTexture.title = "Channels to Texture"; - LGraphChannelsTexture.desc = "Split texture channels"; - LGraphChannelsTexture.widgets_info = { - precision: { widget: "combo", values: LGraphTexture.MODE_VALUES } - }; - - LGraphChannelsTexture.prototype.onExecute = function() { - var white = LGraphTexture.getWhiteTexture(); - var texR = this.getInputData(0) || white; - var texG = this.getInputData(1) || white; - var texB = this.getInputData(2) || white; - var texA = this.getInputData(3) || white; - - gl.disable(gl.BLEND); - gl.disable(gl.DEPTH_TEST); - - var mesh = Mesh.getScreenQuad(); - if (!LGraphChannelsTexture._shader) { - LGraphChannelsTexture._shader = new GL.Shader( - Shader.SCREEN_VERTEX_SHADER, - LGraphChannelsTexture.pixel_shader - ); - } - var shader = LGraphChannelsTexture._shader; - - var w = Math.max(texR.width, texG.width, texB.width, texA.width); - var h = Math.max( - texR.height, - texG.height, - texB.height, - texA.height - ); - var type = - this.properties.precision == LGraphTexture.HIGH - ? LGraphTexture.HIGH_PRECISION_FORMAT - : gl.UNSIGNED_BYTE; - - if ( - !this._texture || - this._texture.width != w || - this._texture.height != h || - this._texture.type != type - ) { - this._texture = new GL.Texture(w, h, { - type: type, - format: gl.RGBA, - filter: gl.LINEAR - }); - } - - var color = this._color; - color[0] = this.properties.R; - color[1] = this.properties.G; - color[2] = this.properties.B; - color[3] = this.properties.A; - var uniforms = this._uniforms; - - this._texture.drawTo(function() { - texR.bind(0); - texG.bind(1); - texB.bind(2); - texA.bind(3); - shader.uniforms(uniforms).draw(mesh); - }); - this.setOutputData(0, this._texture); - }; - - 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\ - uniform vec4 u_color;\n\ - \n\ - void main() {\n\ - gl_FragColor = u_color * 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 Color ***************************************** - function LGraphTextureColor() { - this.addOutput("Texture", "Texture"); - - this._tex_color = vec4.create(); - this.properties = { - color: vec4.create(), - precision: LGraphTexture.DEFAULT - }; - } - - LGraphTextureColor.title = "Color"; - LGraphTextureColor.desc = - "Generates a 1x1 texture with a constant color"; - - LGraphTextureColor.widgets_info = { - precision: { widget: "combo", values: LGraphTexture.MODE_VALUES } - }; - - LGraphTextureColor.prototype.onDrawBackground = function(ctx) { - var c = this.properties.color; - ctx.fillStyle = - "rgb(" + - Math.floor(Math.clamp(c[0], 0, 1) * 255) + - "," + - Math.floor(Math.clamp(c[1], 0, 1) * 255) + - "," + - Math.floor(Math.clamp(c[2], 0, 1) * 255) + - ")"; - if (this.flags.collapsed) { - this.boxcolor = ctx.fillStyle; - } else { - ctx.fillRect(0, 0, this.size[0], this.size[1]); - } - }; - - LGraphTextureColor.prototype.onExecute = function() { - var type = - this.properties.precision == LGraphTexture.HIGH - ? LGraphTexture.HIGH_PRECISION_FORMAT - : gl.UNSIGNED_BYTE; - - if (!this._tex || this._tex.type != type) { - this._tex = new GL.Texture(1, 1, { - format: gl.RGBA, - type: type, - minFilter: gl.NEAREST - }); - } - var color = this.properties.color; - - 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; - } - switch (input.name) { - case "RGB": - case "RGBA": - color.set(v); - break; - case "R": - color[0] = v; - break; - case "G": - color[1] = v; - break; - case "B": - color[2] = v; - break; - case "A": - color[3] = v; - break; - } - } - } - - if (vec4.sqrDist(this._tex_color, color) > 0.001) { - this._tex_color.set(color); - this._tex.fill(color); - } - this.setOutputData(0, this._tex); - }; - - LGraphTextureColor.prototype.onGetInputs = function() { - return [ - ["RGB", "vec3"], - ["RGBA", "vec4"], - ["R", "number"], - ["G", "number"], - ["B", "number"], - ["A", "number"] - ]; - }; - - LiteGraph.registerNodeType("texture/color", LGraphTextureColor); - - // 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 = { factor: 0.5, size_from_biggest: true, invert: false, precision: LGraphTexture.DEFAULT }; - this._uniforms = { - u_textureA: 0, - u_textureB: 1, - u_textureMix: 2, - u_mix: vec4.create() - }; - } - - 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); - if (!texA || !texB) { - return; - } - - var texMix = this.getInputData(2); - - var factor = this.getInputData(3); - - this._tex = LGraphTexture.getTargetTexture( - this.properties.size_from_biggest && texB.width > texA.width ? texB : texA, - this._tex, - this.properties.precision - ); - - gl.disable(gl.BLEND); - gl.disable(gl.DEPTH_TEST); - - var mesh = Mesh.getScreenQuad(); - var shader = null; - var uniforms = this._uniforms; - if (texMix) { - shader = LGraphTextureMix._shader_tex; - if (!shader) { - shader = LGraphTextureMix._shader_tex = new GL.Shader( - Shader.SCREEN_VERTEX_SHADER, - LGraphTextureMix.pixel_shader, - { MIX_TEX: "" } - ); - } - } else { - shader = LGraphTextureMix._shader_factor; - if (!shader) { - shader = LGraphTextureMix._shader_factor = new GL.Shader( - Shader.SCREEN_VERTEX_SHADER, - LGraphTextureMix.pixel_shader - ); - } - var f = factor == null ? this.properties.factor : factor; - uniforms.u_mix.set([f, f, f, f]); - } - - var invert = this.properties.invert; - - this._tex.drawTo(function() { - texA.bind( invert ? 1 : 0 ); - texB.bind( invert ? 0 : 1 ); - if (texMix) { - texMix.bind(2); - } - shader.uniforms(uniforms).draw(mesh); - }); - - this.setOutputData(0, this._tex); - }; - - LGraphTextureMix.prototype.onGetInputs = function() { - return [["factor", "number"]]; - }; - - 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\ - #ifdef MIX_TEX\n\ - uniform sampler2D u_textureMix;\n\ - #else\n\ - uniform vec4 u_mix;\n\ - #endif\n\ - \n\ - void main() {\n\ - #ifdef MIX_TEX\n\ - vec4 f = texture2D(u_textureMix, v_coord);\n\ - #else\n\ - vec4 f = u_mix;\n\ - #endif\n\ - gl_FragColor = mix( texture2D(u_textureA, v_coord), texture2D(u_textureB, v_coord), f );\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 Depth ***************************************** - function LGraphTextureLinearDepth() { - this.addInput("Texture", "Texture"); - this.addOutput("Texture", "Texture"); - this.properties = { - precision: LGraphTexture.DEFAULT, - invert: false - }; - this._uniforms = { - u_texture: 0, - u_near: 0.1, - u_far: 10000 - }; - } - - LGraphTextureLinearDepth.widgets_info = { - precision: { widget: "combo", values: LGraphTexture.MODE_VALUES } - }; - - LGraphTextureLinearDepth.title = "Linear Depth"; - LGraphTextureLinearDepth.desc = "Creates a color texture with linear depth"; - - LGraphTextureLinearDepth.prototype.onExecute = function() { - if (!this.isOutputConnected(0)) { - return; - } //saves work - - var tex = this.getInputData(0); - if (!tex || (tex.format != gl.DEPTH_COMPONENT && tex.format != gl.DEPTH_STENCIL) ) { - return; - } - - var precision = this.properties.precision == LGraphTexture.HIGH ? gl.HIGH_PRECISION_FORMAT : gl.UNSIGNED_BYTE; - - 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.RGB, - filter: gl.LINEAR - }); - } - - var uniforms = this._uniforms; - - uniforms.u_near = tex.near_far_planes[0]; - uniforms.u_far = tex.near_far_planes[1]; - uniforms.u_invert = this.properties.invert ? 1 : 0; - - gl.disable(gl.BLEND); - gl.disable(gl.DEPTH_TEST); - var mesh = Mesh.getScreenQuad(); - if(!LGraphTextureLinearDepth._shader) - LGraphTextureLinearDepth._shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureLinearDepth.pixel_shader); - var shader = LGraphTextureLinearDepth._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); - }; - - LGraphTextureLinearDepth.pixel_shader = - "precision highp float;\n\ - precision highp float;\n\ - varying vec2 v_coord;\n\ - uniform sampler2D u_texture;\n\ - uniform float u_near;\n\ - uniform float u_far;\n\ - uniform int u_invert;\n\ - \n\ - void main() {\n\ - float zNear = u_near;\n\ - float zFar = u_far;\n\ - float depth = texture2D(u_texture, v_coord).x;\n\ - depth = depth * 2.0 - 1.0;\n\ - float f = zNear * (depth + 1.0) / (zFar + zNear - depth * (zFar - zNear));\n\ - if( u_invert == 1 )\n\ - f = 1.0 - f;\n\ - gl_FragColor = vec4(vec3(f),1.0);\n\ - }\n\ - "; - - LiteGraph.registerNodeType( "texture/linear_depth", LGraphTextureLinearDepth ); - - // 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 Filter ***************************************** - 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 - ) { - this._temp_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 ***************************************** - function LGraphTextureXDoGFilter() { - this.addInput("Texture", "Texture"); - this.addOutput("Filtered", "Texture"); - this.properties = { - sigma: 1.4, - k: 1.6, - p: 21.7, - epsilon: 79, - phi: 0.017 - }; - } - - LGraphTextureXDoGFilter.title = "XDoG Filter"; - LGraphTextureXDoGFilter.desc = - "Filters a texture giving an artistic ink style"; - - LGraphTextureXDoGFilter.max_radius = 10; - LGraphTextureXDoGFilter._shaders = []; - - LGraphTextureXDoGFilter.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 - ) { - this._temp_texture = new GL.Texture(tex.width, tex.height, { - type: tex.type, - format: gl.RGBA, - filter: gl.LINEAR - }); - } - - if (!LGraphTextureXDoGFilter._xdog_shader) { - LGraphTextureXDoGFilter._xdog_shader = new GL.Shader( - Shader.SCREEN_VERTEX_SHADER, - LGraphTextureXDoGFilter.xdog_pixel_shader - ); - } - var shader = LGraphTextureXDoGFilter._xdog_shader; - var mesh = GL.Mesh.getScreenQuad(); - - var sigma = this.properties.sigma; - var k = this.properties.k; - var p = this.properties.p; - var epsilon = this.properties.epsilon; - var phi = this.properties.phi; - tex.bind(0); - this._temp_texture.drawTo(function() { - shader - .uniforms({ - src: 0, - sigma: sigma, - k: k, - p: p, - epsilon: epsilon, - phi: phi, - cvsWidth: tex.width, - cvsHeight: tex.height - }) - .draw(mesh); - }); - - this.setOutputData(0, this._temp_texture); - }; - - //from https://github.com/RaymondMcGuire/GPU-Based-Image-Processing-Tools/blob/master/lib_webgl/scripts/main.js - LGraphTextureXDoGFilter.xdog_pixel_shader = - "\n\ -precision highp float;\n\ -uniform sampler2D src;\n\n\ -uniform float cvsHeight;\n\ -uniform float cvsWidth;\n\n\ -uniform float sigma;\n\ -uniform float k;\n\ -uniform float p;\n\ -uniform float epsilon;\n\ -uniform float phi;\n\ -varying vec2 v_coord;\n\n\ -float cosh(float val)\n\ -{\n\ - float tmp = exp(val);\n\ - float cosH = (tmp + 1.0 / tmp) / 2.0;\n\ - return cosH;\n\ -}\n\n\ -float tanh(float val)\n\ -{\n\ - float tmp = exp(val);\n\ - float tanH = (tmp - 1.0 / tmp) / (tmp + 1.0 / tmp);\n\ - return tanH;\n\ -}\n\n\ -float sinh(float val)\n\ -{\n\ - float tmp = exp(val);\n\ - float sinH = (tmp - 1.0 / tmp) / 2.0;\n\ - return sinH;\n\ -}\n\n\ -void main(void){\n\ - vec3 destColor = vec3(0.0);\n\ - float tFrag = 1.0 / cvsHeight;\n\ - float sFrag = 1.0 / cvsWidth;\n\ - vec2 Frag = vec2(sFrag,tFrag);\n\ - vec2 uv = gl_FragCoord.st;\n\ - float twoSigmaESquared = 2.0 * sigma * sigma;\n\ - float twoSigmaRSquared = twoSigmaESquared * k * k;\n\ - int halfWidth = int(ceil( 1.0 * sigma * k ));\n\n\ - const int MAX_NUM_ITERATION = 99999;\n\ - vec2 sum = vec2(0.0);\n\ - vec2 norm = vec2(0.0);\n\n\ - for(int cnt=0;cnt (2*halfWidth+1)*(2*halfWidth+1)){break;}\n\ - int i = int(cnt / (2*halfWidth+1)) - halfWidth;\n\ - int j = cnt - halfWidth - int(cnt / (2*halfWidth+1)) * (2*halfWidth+1);\n\n\ - float d = length(vec2(i,j));\n\ - vec2 kernel = vec2( exp( -d * d / twoSigmaESquared ), \n\ - exp( -d * d / twoSigmaRSquared ));\n\n\ - vec2 L = texture2D(src, (uv + vec2(i,j)) * Frag).xx;\n\n\ - norm += kernel;\n\ - sum += kernel * L;\n\ - }\n\n\ - sum /= norm;\n\n\ - float H = 100.0 * ((1.0 + p) * sum.x - p * sum.y);\n\ - float edge = ( H > epsilon )? 1.0 : 1.0 + tanh( phi * (H - epsilon));\n\ - destColor = vec3(edge);\n\ - gl_FragColor = vec4(destColor, 1.0);\n\ -}"; - - LiteGraph.registerNodeType("texture/xDoG", LGraphTextureXDoGFilter); - - // Texture Webcam ***************************************** - function LGraphTextureWebcam() { - this.addOutput("Webcam", "Texture"); - this.properties = { texture_name: "", facingMode: "user" }; - this.boxcolor = "black"; - this.version = 0; - } - - LGraphTextureWebcam.title = "Webcam"; - LGraphTextureWebcam.desc = "Webcam texture"; - - LGraphTextureWebcam.is_webcam_open = false; - - LGraphTextureWebcam.prototype.openStream = function() { - if (!navigator.getUserMedia) { - //console.log('getUserMedia() is not supported in your browser, use chrome and enable WebRTC from about://flags'); - return; - } - - this._waiting_confirmation = true; - - // Not showing vendor prefixes. - var constraints = { - audio: false, - video: { facingMode: this.properties.facingMode } - }; - navigator.mediaDevices - .getUserMedia(constraints) - .then(this.streamReady.bind(this)) - .catch(onFailSoHard); - - var that = this; - function onFailSoHard(e) { - LGraphTextureWebcam.is_webcam_open = false; - console.log("Webcam rejected", e); - that._webcam_stream = false; - that.boxcolor = "red"; - that.trigger("stream_error"); - } - }; - - LGraphTextureWebcam.prototype.closeStream = function() { - if (this._webcam_stream) { - var tracks = this._webcam_stream.getTracks(); - if (tracks.length) { - for (var i = 0; i < tracks.length; ++i) { - tracks[i].stop(); - } - } - LGraphTextureWebcam.is_webcam_open = false; - this._webcam_stream = null; - this._video = null; - this.boxcolor = "black"; - this.trigger("stream_closed"); - } - }; - - LGraphTextureWebcam.prototype.streamReady = function(localMediaStream) { - this._webcam_stream = localMediaStream; - //this._waiting_confirmation = false; - this.boxcolor = "green"; - var video = this._video; - if (!video) { - video = document.createElement("video"); - video.autoplay = true; - video.srcObject = 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. - LGraphTextureWebcam.is_webcam_open = true; - console.log(e); - }; - } - this.trigger("stream_ready", video); - }; - - LGraphTextureWebcam.prototype.onPropertyChanged = function( - name, - value - ) { - if (name == "facingMode") { - this.properties.facingMode = value; - this.closeStream(); - this.openStream(); - } - }; - - LGraphTextureWebcam.prototype.onRemoved = function() { - if (!this._webcam_stream) { - return; - } - - var tracks = this._webcam_stream.getTracks(); - if (tracks.length) { - for (var i = 0; i < tracks.length; ++i) { - tracks[i].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.drawImage(this._video, 0, 0, this.size[0], this.size[1]); - } else { - if (this._video_texture) { - ctx.drawImage( - this._video_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._video_texture; - if (!temp || temp.width != width || temp.height != height) { - this._video_texture = new GL.Texture(width, height, { - format: gl.RGB, - filter: gl.LINEAR - }); - } - - this._video_texture.uploadImage(this._video); - this._video_texture.version = ++this.version; - - if (this.properties.texture_name) { - var container = LGraphTexture.getTexturesContainer(); - container[this.properties.texture_name] = this._video_texture; - } - - this.setOutputData(0, this._video_texture); - for (var i = 1; i < this.outputs.length; ++i) { - if (!this.outputs[i]) { - continue; - } - switch (this.outputs[i].name) { - case "width": - this.setOutputData(i, this._video.videoWidth); - break; - case "height": - this.setOutputData(i, this._video.videoHeight); - break; - } - } - }; - - LGraphTextureWebcam.prototype.onGetOutputs = function() { - return [ - ["width", "number"], - ["height", "number"], - ["stream_ready", LiteGraph.EVENT], - ["stream_closed", LiteGraph.EVENT], - ["stream_error", LiteGraph.EVENT] - ]; - }; - - 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= res)\n\ - break;\n\ - iCount++;\n\ - }\n\ - float nf = n/normK;\n\ - return nf*nf*nf*nf;\n\ - }\n\ - void main() {\n\ - vec2 uv = v_coord * u_scale * u_viewport + u_offset * u_scale;\n\ - vec4 color = vec4( pNoise( uv, u_octaves ) * u_amplitude );\n\ - gl_FragColor = color;\n\ - }"; - - LiteGraph.registerNodeType("texture/perlin", LGraphTexturePerlin); - - function LGraphTextureCanvas2D() { - this.addInput("v"); - this.addOutput("out", "Texture"); - this.properties = { - code: "", - width: 512, - height: 512, - clear: true, - precision: LGraphTexture.DEFAULT, - use_html_canvas: false - }; - this._func = null; - this._temp_texture = null; - } - - LGraphTextureCanvas2D.title = "Canvas2D"; - LGraphTextureCanvas2D.desc = "Executes Canvas2D code inside a texture or the viewport."; - LGraphTextureCanvas2D.help = "Set width and height to 0 to match viewport size."; - - LGraphTextureCanvas2D.widgets_info = { - precision: { widget: "combo", values: LGraphTexture.MODE_VALUES }, - code: { type: "code" }, - width: { type: "Number", precision: 0, step: 1 }, - height: { type: "Number", precision: 0, step: 1 } - }; - - LGraphTextureCanvas2D.prototype.onPropertyChanged = function( name, value ) { - if (name == "code" ) - this.compileCode( value ); - } - - LGraphTextureCanvas2D.prototype.compileCode = function( code ) { - this._func = null; - if( !LiteGraph.allow_scripts ) - return; - - try { - this._func = new Function( "canvas", "ctx", "time", "script","v", code ); - this.boxcolor = "#00FF00"; - } catch (err) { - this.boxcolor = "#FF0000"; - console.error("Error parsing script"); - console.error(err); - } - }; - - LGraphTextureCanvas2D.prototype.onExecute = function() { - var func = this._func; - if (!func || !this.isOutputConnected(0)) { - return; - } - this.executeDraw( func ); - } - - LGraphTextureCanvas2D.prototype.executeDraw = function( func_context ) { - - var width = this.properties.width || gl.canvas.width; - var height = this.properties.height || gl.canvas.height; - var temp = this._temp_texture; - var type = LGraphTexture.getTextureType( this.properties.precision ); - if (!temp || temp.width != width || temp.height != height || temp.type != type ) { - temp = this._temp_texture = new GL.Texture(width, height, { - format: gl.RGBA, - filter: gl.LINEAR, - type: type - }); - } - - var v = this.getInputData(0); - - var properties = this.properties; - var that = this; - var time = this.graph.getTime(); - var ctx = gl; - var canvas = gl.canvas; - if( this.properties.use_html_canvas || !global.enableWebGLCanvas ) - { - if(!this._canvas) - { - canvas = this._canvas = createCanvas(width.height); - ctx = this._ctx = canvas.getContext("2d"); - } - else - { - canvas = this._canvas; - ctx = this._ctx; - } - canvas.width = width; - canvas.height = height; - } - - if(ctx == gl) //using Canvas2DtoWebGL - temp.drawTo(function() { - gl.start2D(); - if(properties.clear) - { - gl.clearColor(0,0,0,0); - gl.clear( gl.COLOR_BUFFER_BIT ); - } - - try { - if (func_context.draw) { - func_context.draw.call(that, canvas, ctx, time, func_context, v); - } else { - func_context.call(that, canvas, ctx, time, func_context,v); - } - that.boxcolor = "#00FF00"; - } catch (err) { - that.boxcolor = "#FF0000"; - console.error("Error executing script"); - console.error(err); - } - gl.finish2D(); - }); - else //rendering to offscren canvas and uploading to texture - { - if(properties.clear) - ctx.clearRect(0,0,canvas.width,canvas.height); - - try { - if (func_context.draw) { - func_context.draw.call(this, canvas, ctx, time, func_context, v); - } else { - func_context.call(this, canvas, ctx, time, func_context,v); - } - this.boxcolor = "#00FF00"; - } catch (err) { - this.boxcolor = "#FF0000"; - console.error("Error executing script"); - console.error(err); - } - temp.uploadImage( canvas ); - } - - this.setOutputData(0, temp); - }; - - LiteGraph.registerNodeType("texture/canvas2D", LGraphTextureCanvas2D); - - // To do chroma keying ***************** - - function LGraphTextureMatte() { - this.addInput("in", "Texture"); - - this.addOutput("out", "Texture"); - this.properties = { - key_color: vec3.fromValues(0, 1, 0), - threshold: 0.8, - slope: 0.2, - precision: LGraphTexture.DEFAULT - }; - } - - LGraphTextureMatte.title = "Matte"; - LGraphTextureMatte.desc = "Extracts background"; - - LGraphTextureMatte.widgets_info = { - key_color: { widget: "color" }, - precision: { widget: "combo", values: LGraphTexture.MODE_VALUES } - }; - - LGraphTextureMatte.prototype.onExecute = function() { - if (!this.isOutputConnected(0)) { - 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); - - if (!this._uniforms) { - this._uniforms = { - u_texture: 0, - u_key_color: this.properties.key_color, - u_threshold: 1, - u_slope: 1 - }; - } - var uniforms = this._uniforms; - - var mesh = Mesh.getScreenQuad(); - var shader = LGraphTextureMatte._shader; - if (!shader) { - shader = LGraphTextureMatte._shader = new GL.Shader( - GL.Shader.SCREEN_VERTEX_SHADER, - LGraphTextureMatte.pixel_shader - ); - } - - uniforms.u_key_color = this.properties.key_color; - uniforms.u_threshold = this.properties.threshold; - uniforms.u_slope = this.properties.slope; - - this._tex.drawTo(function() { - tex.bind(0); - shader.uniforms(uniforms).draw(mesh); - }); - - this.setOutputData(0, this._tex); - }; - - LGraphTextureMatte.pixel_shader = - "precision highp float;\n\ - varying vec2 v_coord;\n\ - uniform sampler2D u_texture;\n\ - uniform vec3 u_key_color;\n\ - uniform float u_threshold;\n\ - uniform float u_slope;\n\ - \n\ - void main() {\n\ - vec3 color = texture2D( u_texture, v_coord ).xyz;\n\ - float diff = length( normalize(color) - normalize(u_key_color) );\n\ - float edge = u_threshold * (1.0 - u_slope);\n\ - float alpha = smoothstep( edge, u_threshold, diff);\n\ - gl_FragColor = vec4( color, alpha );\n\ - }"; - - LiteGraph.registerNodeType("texture/matte", LGraphTextureMatte); - - //*********************************** - function LGraphCubemapToTexture2D() { - this.addInput("in", "texture"); - this.addInput("yaw", "number"); - this.addOutput("out", "texture"); - this.properties = { yaw: 0 }; - } - - LGraphCubemapToTexture2D.title = "CubemapToTexture2D"; - LGraphCubemapToTexture2D.desc = "Transforms a CUBEMAP texture into a TEXTURE2D in Polar Representation"; - - LGraphCubemapToTexture2D.prototype.onExecute = function() { - if (!this.isOutputConnected(0)) - return; - - var tex = this.getInputData(0); - if ( !tex || tex.texture_type != GL.TEXTURE_CUBE_MAP ) - return; - if( this._last_tex && ( this._last_tex.height != tex.height || this._last_tex.type != tex.type )) - this._last_tex = null; - var yaw = this.getInputOrProperty("yaw"); - this._last_tex = GL.Texture.cubemapToTexture2D( tex, tex.height, this._last_tex, true, yaw ); - this.setOutputData( 0, this._last_tex ); - }; - - LiteGraph.registerNodeType( "texture/cubemapToTexture2D", LGraphCubemapToTexture2D ); -})(this); - -(function(global) { - var LiteGraph = global.LiteGraph; - - //Works with Litegl.js to create WebGL nodes - if (typeof GL != "undefined") { - // Texture Lens ***************************************** - function LGraphFXLens() { - this.addInput("Texture", "Texture"); - this.addInput("Aberration", "number"); - this.addInput("Distortion", "number"); - this.addInput("Blur", "number"); - this.addOutput("Texture", "Texture"); - this.properties = { - aberration: 1.0, - distortion: 1.0, - blur: 1.0, - precision: LGraphTexture.DEFAULT - }; - - if (!LGraphFXLens._shader) { - LGraphFXLens._shader = new GL.Shader( - GL.Shader.SCREEN_VERTEX_SHADER, - LGraphFXLens.pixel_shader - ); - LGraphFXLens._texture = new GL.Texture(3, 1, { - format: gl.RGB, - wrap: gl.CLAMP_TO_EDGE, - magFilter: gl.LINEAR, - minFilter: gl.LINEAR, - pixel_data: [255, 0, 0, 0, 255, 0, 0, 0, 255] - }); - } - } - - LGraphFXLens.title = "Lens"; - LGraphFXLens.desc = "Camera Lens distortion"; - LGraphFXLens.widgets_info = { - precision: { widget: "combo", values: LGraphTexture.MODE_VALUES } - }; - - LGraphFXLens.prototype.onExecute = function() { - 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 - ); - - var aberration = this.properties.aberration; - if (this.isInputConnected(1)) { - aberration = this.getInputData(1); - this.properties.aberration = aberration; - } - - var distortion = this.properties.distortion; - if (this.isInputConnected(2)) { - distortion = this.getInputData(2); - this.properties.distortion = distortion; - } - - var blur = this.properties.blur; - if (this.isInputConnected(3)) { - blur = this.getInputData(3); - this.properties.blur = blur; - } - - gl.disable(gl.BLEND); - gl.disable(gl.DEPTH_TEST); - var mesh = Mesh.getScreenQuad(); - var shader = LGraphFXLens._shader; - //var camera = LS.Renderer._current_camera; - - this._tex.drawTo(function() { - tex.bind(0); - shader - .uniforms({ - u_texture: 0, - u_aberration: aberration, - u_distortion: distortion, - u_blur: blur - }) - .draw(mesh); - }); - - this.setOutputData(0, this._tex); - }; - - LGraphFXLens.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_aberration;\n\ - uniform float u_distortion;\n\ - uniform float u_blur;\n\ - \n\ - void main() {\n\ - vec2 coord = v_coord;\n\ - float dist = distance(vec2(0.5), coord);\n\ - vec2 dist_coord = coord - vec2(0.5);\n\ - float percent = 1.0 + ((0.5 - dist) / 0.5) * u_distortion;\n\ - dist_coord *= percent;\n\ - coord = dist_coord + vec2(0.5);\n\ - vec4 color = texture2D(u_texture,coord, u_blur * dist);\n\ - color.r = texture2D(u_texture,vec2(0.5) + dist_coord * (1.0+0.01*u_aberration), u_blur * dist ).r;\n\ - color.b = texture2D(u_texture,vec2(0.5) + dist_coord * (1.0-0.01*u_aberration), u_blur * dist ).b;\n\ - gl_FragColor = color;\n\ - }\n\ - "; - /* - float normalized_tunable_sigmoid(float xs, float k)\n\ - {\n\ - xs = xs * 2.0 - 1.0;\n\ - float signx = sign(xs);\n\ - float absx = abs(xs);\n\ - return signx * ((-k - 1.0)*absx)/(2.0*(-2.0*k*absx+k-1.0)) + 0.5;\n\ - }\n\ - */ - - LiteGraph.registerNodeType("fx/lens", LGraphFXLens); - global.LGraphFXLens = LGraphFXLens; - - /* not working yet - function LGraphDepthOfField() - { - this.addInput("Color","Texture"); - this.addInput("Linear Depth","Texture"); - this.addInput("Camera","camera"); - this.addOutput("Texture","Texture"); - this.properties = { high_precision: false }; - } - - LGraphDepthOfField.title = "Depth Of Field"; - LGraphDepthOfField.desc = "Applies a depth of field effect"; - - LGraphDepthOfField.prototype.onExecute = function() - { - var tex = this.getInputData(0); - var depth = this.getInputData(1); - var camera = this.getInputData(2); - - if(!tex || !depth || !camera) - { - this.setOutputData(0, 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 shader = LGraphDepthOfField._shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphDepthOfField._pixel_shader ); - - var screen_mesh = Mesh.getScreenQuad(); - - gl.disable( gl.DEPTH_TEST ); - gl.disable( gl.BLEND ); - - var camera_position = camera.getEye(); - var focus_point = camera.getCenter(); - var distance = vec3.distance( camera_position, focus_point ); - var far = camera.far; - var focus_range = distance * 0.5; - - this._temp_texture.drawTo( function() { - tex.bind(0); - depth.bind(1); - shader.uniforms({u_texture:0, u_depth_texture:1, u_resolution: [1/tex.width, 1/tex.height], u_far: far, u_focus_point: distance, u_focus_scale: focus_range }).draw(screen_mesh); - }); - - this.setOutputData(0, this._temp_texture); - } - - //from http://tuxedolabs.blogspot.com.es/2018/05/bokeh-depth-of-field-in-single-pass.html - LGraphDepthOfField._pixel_shader = "\n\ - precision highp float;\n\ - varying vec2 v_coord;\n\ - uniform sampler2D u_texture; //Image to be processed\n\ - uniform sampler2D u_depth_texture; //Linear depth, where 1.0 == far plane\n\ - uniform vec2 u_iresolution; //The size of a pixel: vec2(1.0/width, 1.0/height)\n\ - uniform float u_far; // Far plane\n\ - uniform float u_focus_point;\n\ - uniform float u_focus_scale;\n\ - \n\ - const float GOLDEN_ANGLE = 2.39996323;\n\ - const float MAX_BLUR_SIZE = 20.0;\n\ - const float RAD_SCALE = 0.5; // Smaller = nicer blur, larger = faster\n\ - \n\ - float getBlurSize(float depth, float focusPoint, float focusScale)\n\ - {\n\ - float coc = clamp((1.0 / focusPoint - 1.0 / depth)*focusScale, -1.0, 1.0);\n\ - return abs(coc) * MAX_BLUR_SIZE;\n\ - }\n\ - \n\ - vec3 depthOfField(vec2 texCoord, float focusPoint, float focusScale)\n\ - {\n\ - float centerDepth = texture2D(u_depth_texture, texCoord).r * u_far;\n\ - float centerSize = getBlurSize(centerDepth, focusPoint, focusScale);\n\ - vec3 color = texture2D(u_texture, v_coord).rgb;\n\ - float tot = 1.0;\n\ - \n\ - float radius = RAD_SCALE;\n\ - for (float ang = 0.0; ang < 100.0; ang += GOLDEN_ANGLE)\n\ - {\n\ - vec2 tc = texCoord + vec2(cos(ang), sin(ang)) * u_iresolution * radius;\n\ - \n\ - vec3 sampleColor = texture2D(u_texture, tc).rgb;\n\ - float sampleDepth = texture2D(u_depth_texture, tc).r * u_far;\n\ - float sampleSize = getBlurSize( sampleDepth, focusPoint, focusScale );\n\ - if (sampleDepth > centerDepth)\n\ - sampleSize = clamp(sampleSize, 0.0, centerSize*2.0);\n\ - \n\ - float m = smoothstep(radius-0.5, radius+0.5, sampleSize);\n\ - color += mix(color/tot, sampleColor, m);\n\ - tot += 1.0;\n\ - radius += RAD_SCALE/radius;\n\ - if(radius>=MAX_BLUR_SIZE)\n\ - return color / tot;\n\ - }\n\ - return color / tot;\n\ - }\n\ - void main()\n\ - {\n\ - gl_FragColor = vec4( depthOfField( v_coord, u_focus_point, u_focus_scale ), 1.0 );\n\ - //gl_FragColor = vec4( texture2D(u_depth_texture, v_coord).r );\n\ - }\n\ - "; - - LiteGraph.registerNodeType("fx/DOF", LGraphDepthOfField ); - global.LGraphDepthOfField = LGraphDepthOfField; - */ - - //******************************************************* - - function LGraphFXBokeh() { - this.addInput("Texture", "Texture"); - this.addInput("Blurred", "Texture"); - this.addInput("Mask", "Texture"); - this.addInput("Threshold", "number"); - this.addOutput("Texture", "Texture"); - this.properties = { - shape: "", - size: 10, - alpha: 1.0, - threshold: 1.0, - high_precision: false - }; - } - - LGraphFXBokeh.title = "Bokeh"; - LGraphFXBokeh.desc = "applies an Bokeh effect"; - - LGraphFXBokeh.widgets_info = { shape: { widget: "texture" } }; - - LGraphFXBokeh.prototype.onExecute = function() { - var tex = this.getInputData(0); - var blurred_tex = this.getInputData(1); - var mask_tex = this.getInputData(2); - if (!tex || !mask_tex || !this.properties.shape) { - this.setOutputData(0, tex); - return; - } - - if (!blurred_tex) { - blurred_tex = tex; - } - - var shape_tex = LGraphTexture.getTexture(this.properties.shape); - if (!shape_tex) { - return; - } - - var threshold = this.properties.threshold; - if (this.isInputConnected(3)) { - threshold = this.getInputData(3); - this.properties.threshold = threshold; - } - - 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 - }); - } - - //iterations - var size = this.properties.size; - - var first_shader = LGraphFXBokeh._first_shader; - if (!first_shader) { - first_shader = LGraphFXBokeh._first_shader = new GL.Shader( - Shader.SCREEN_VERTEX_SHADER, - LGraphFXBokeh._first_pixel_shader - ); - } - - var second_shader = LGraphFXBokeh._second_shader; - if (!second_shader) { - second_shader = LGraphFXBokeh._second_shader = new GL.Shader( - LGraphFXBokeh._second_vertex_shader, - LGraphFXBokeh._second_pixel_shader - ); - } - - var points_mesh = this._points_mesh; - if ( - !points_mesh || - points_mesh._width != tex.width || - points_mesh._height != tex.height || - points_mesh._spacing != 2 - ) { - points_mesh = this.createPointsMesh(tex.width, tex.height, 2); - } - - var screen_mesh = Mesh.getScreenQuad(); - - var point_size = this.properties.size; - var min_light = this.properties.min_light; - var alpha = this.properties.alpha; - - gl.disable(gl.DEPTH_TEST); - gl.disable(gl.BLEND); - - this._temp_texture.drawTo(function() { - tex.bind(0); - blurred_tex.bind(1); - mask_tex.bind(2); - first_shader - .uniforms({ - u_texture: 0, - u_texture_blur: 1, - u_mask: 2, - u_texsize: [tex.width, tex.height] - }) - .draw(screen_mesh); - }); - - this._temp_texture.drawTo(function() { - //clear because we use blending - //gl.clearColor(0.0,0.0,0.0,1.0); - //gl.clear( gl.COLOR_BUFFER_BIT ); - gl.enable(gl.BLEND); - gl.blendFunc(gl.ONE, gl.ONE); - - tex.bind(0); - shape_tex.bind(3); - second_shader - .uniforms({ - u_texture: 0, - u_mask: 2, - u_shape: 3, - u_alpha: alpha, - u_threshold: threshold, - u_pointSize: point_size, - u_itexsize: [1.0 / tex.width, 1.0 / tex.height] - }) - .draw(points_mesh, gl.POINTS); - }); - - this.setOutputData(0, this._temp_texture); - }; - - LGraphFXBokeh.prototype.createPointsMesh = function( - width, - height, - spacing - ) { - var nwidth = Math.round(width / spacing); - var nheight = Math.round(height / spacing); - - var vertices = new Float32Array(nwidth * nheight * 2); - - var ny = -1; - var dx = (2 / width) * spacing; - var dy = (2 / height) * spacing; - for (var y = 0; y < nheight; ++y) { - var nx = -1; - for (var x = 0; x < nwidth; ++x) { - var pos = y * nwidth * 2 + x * 2; - vertices[pos] = nx; - vertices[pos + 1] = ny; - nx += dx; - } - ny += dy; - } - - this._points_mesh = GL.Mesh.load({ vertices2D: vertices }); - this._points_mesh._width = width; - this._points_mesh._height = height; - this._points_mesh._spacing = spacing; - - return this._points_mesh; - }; - - /* - LGraphTextureBokeh._pixel_shader = "precision highp float;\n\ - varying vec2 a_coord;\n\ - uniform sampler2D u_texture;\n\ - uniform sampler2D u_shape;\n\ - \n\ - void main() {\n\ - vec4 color = texture2D( u_texture, gl_PointCoord );\n\ - color *= v_color * u_alpha;\n\ - gl_FragColor = color;\n\ - }\n"; - */ - - LGraphFXBokeh._first_pixel_shader = - "precision highp float;\n\ - precision highp float;\n\ - varying vec2 v_coord;\n\ - uniform sampler2D u_texture;\n\ - uniform sampler2D u_texture_blur;\n\ - uniform sampler2D u_mask;\n\ - \n\ - void main() {\n\ - vec4 color = texture2D(u_texture, v_coord);\n\ - vec4 blurred_color = texture2D(u_texture_blur, v_coord);\n\ - float mask = texture2D(u_mask, v_coord).x;\n\ - gl_FragColor = mix(color, blurred_color, mask);\n\ - }\n\ - "; - - LGraphFXBokeh._second_vertex_shader = - "precision highp float;\n\ - attribute vec2 a_vertex2D;\n\ - varying vec4 v_color;\n\ - uniform sampler2D u_texture;\n\ - uniform sampler2D u_mask;\n\ - uniform vec2 u_itexsize;\n\ - uniform float u_pointSize;\n\ - uniform float u_threshold;\n\ - void main() {\n\ - vec2 coord = a_vertex2D * 0.5 + 0.5;\n\ - v_color = texture2D( u_texture, coord );\n\ - v_color += texture2D( u_texture, coord + vec2(u_itexsize.x, 0.0) );\n\ - v_color += texture2D( u_texture, coord + vec2(0.0, u_itexsize.y));\n\ - v_color += texture2D( u_texture, coord + u_itexsize);\n\ - v_color *= 0.25;\n\ - float mask = texture2D(u_mask, coord).x;\n\ - float luminance = length(v_color) * mask;\n\ - /*luminance /= (u_pointSize*u_pointSize)*0.01 */;\n\ - luminance -= u_threshold;\n\ - if(luminance < 0.0)\n\ - {\n\ - gl_Position.x = -100.0;\n\ - return;\n\ - }\n\ - gl_PointSize = u_pointSize;\n\ - gl_Position = vec4(a_vertex2D,0.0,1.0);\n\ - }\n\ - "; - - LGraphFXBokeh._second_pixel_shader = - "precision highp float;\n\ - varying vec4 v_color;\n\ - uniform sampler2D u_shape;\n\ - uniform float u_alpha;\n\ - \n\ - void main() {\n\ - vec4 color = texture2D( u_shape, gl_PointCoord );\n\ - color *= v_color * u_alpha;\n\ - gl_FragColor = color;\n\ - }\n"; - - LiteGraph.registerNodeType("fx/bokeh", LGraphFXBokeh); - global.LGraphFXBokeh = LGraphFXBokeh; - - //************************************************ - - function LGraphFXGeneric() { - this.addInput("Texture", "Texture"); - this.addInput("value1", "number"); - this.addInput("value2", "number"); - this.addOutput("Texture", "Texture"); - this.properties = { - fx: "halftone", - value1: 1, - value2: 1, - precision: LGraphTexture.DEFAULT - }; - } - - LGraphFXGeneric.title = "FX"; - LGraphFXGeneric.desc = "applies an FX from a list"; - - LGraphFXGeneric.widgets_info = { - fx: { - widget: "combo", - values: ["halftone", "pixelate", "lowpalette", "noise", "gamma"] - }, - precision: { widget: "combo", values: LGraphTexture.MODE_VALUES } - }; - LGraphFXGeneric.shaders = {}; - - LGraphFXGeneric.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 - ); - - //iterations - var value1 = this.properties.value1; - if (this.isInputConnected(1)) { - value1 = this.getInputData(1); - this.properties.value1 = value1; - } - - var value2 = this.properties.value2; - if (this.isInputConnected(2)) { - value2 = this.getInputData(2); - this.properties.value2 = value2; - } - - var fx = this.properties.fx; - var shader = LGraphFXGeneric.shaders[fx]; - if (!shader) { - var pixel_shader_code = LGraphFXGeneric["pixel_shader_" + fx]; - if (!pixel_shader_code) { - return; - } - - shader = LGraphFXGeneric.shaders[fx] = new GL.Shader( - Shader.SCREEN_VERTEX_SHADER, - pixel_shader_code - ); - } - - gl.disable(gl.BLEND); - gl.disable(gl.DEPTH_TEST); - var mesh = Mesh.getScreenQuad(); - var camera = global.LS ? LS.Renderer._current_camera : null; - var camera_planes; - if (camera) { - camera_planes = [ - LS.Renderer._current_camera.near, - LS.Renderer._current_camera.far - ]; - } else { - camera_planes = [1, 100]; - } - - var noise = null; - if (fx == "noise") { - noise = LGraphTexture.getNoiseTexture(); - } - - this._tex.drawTo(function() { - tex.bind(0); - if (fx == "noise") { - noise.bind(1); - } - - shader - .uniforms({ - u_texture: 0, - u_noise: 1, - u_size: [tex.width, tex.height], - u_rand: [Math.random(), Math.random()], - u_value1: value1, - u_value2: value2, - u_camera_planes: camera_planes - }) - .draw(mesh); - }); - - this.setOutputData(0, this._tex); - }; - - LGraphFXGeneric.pixel_shader_halftone = - "precision highp float;\n\ - varying vec2 v_coord;\n\ - uniform sampler2D u_texture;\n\ - uniform vec2 u_camera_planes;\n\ - uniform vec2 u_size;\n\ - uniform float u_value1;\n\ - uniform float u_value2;\n\ - \n\ - float pattern() {\n\ - float s = sin(u_value1 * 3.1415), c = cos(u_value1 * 3.1415);\n\ - vec2 tex = v_coord * u_size.xy;\n\ - vec2 point = vec2(\n\ - c * tex.x - s * tex.y ,\n\ - s * tex.x + c * tex.y \n\ - ) * u_value2;\n\ - return (sin(point.x) * sin(point.y)) * 4.0;\n\ - }\n\ - void main() {\n\ - vec4 color = texture2D(u_texture, v_coord);\n\ - float average = (color.r + color.g + color.b) / 3.0;\n\ - gl_FragColor = vec4(vec3(average * 10.0 - 5.0 + pattern()), color.a);\n\ - }\n"; - - LGraphFXGeneric.pixel_shader_pixelate = - "precision highp float;\n\ - varying vec2 v_coord;\n\ - uniform sampler2D u_texture;\n\ - uniform vec2 u_camera_planes;\n\ - uniform vec2 u_size;\n\ - uniform float u_value1;\n\ - uniform float u_value2;\n\ - \n\ - void main() {\n\ - vec2 coord = vec2( floor(v_coord.x * u_value1) / u_value1, floor(v_coord.y * u_value2) / u_value2 );\n\ - vec4 color = texture2D(u_texture, coord);\n\ - gl_FragColor = color;\n\ - }\n"; - - LGraphFXGeneric.pixel_shader_lowpalette = - "precision highp float;\n\ - varying vec2 v_coord;\n\ - uniform sampler2D u_texture;\n\ - uniform vec2 u_camera_planes;\n\ - uniform vec2 u_size;\n\ - uniform float u_value1;\n\ - uniform float u_value2;\n\ - \n\ - void main() {\n\ - vec4 color = texture2D(u_texture, v_coord);\n\ - gl_FragColor = floor(color * u_value1) / u_value1;\n\ - }\n"; - - LGraphFXGeneric.pixel_shader_noise = - "precision highp float;\n\ - varying vec2 v_coord;\n\ - uniform sampler2D u_texture;\n\ - uniform sampler2D u_noise;\n\ - uniform vec2 u_size;\n\ - uniform float u_value1;\n\ - uniform float u_value2;\n\ - uniform vec2 u_rand;\n\ - \n\ - void main() {\n\ - vec4 color = texture2D(u_texture, v_coord);\n\ - vec3 noise = texture2D(u_noise, v_coord * vec2(u_size.x / 512.0, u_size.y / 512.0) + u_rand).xyz - vec3(0.5);\n\ - gl_FragColor = vec4( color.xyz + noise * u_value1, color.a );\n\ - }\n"; - - LGraphFXGeneric.pixel_shader_gamma = - "precision highp float;\n\ - varying vec2 v_coord;\n\ - uniform sampler2D u_texture;\n\ - uniform float u_value1;\n\ - \n\ - void main() {\n\ - vec4 color = texture2D(u_texture, v_coord);\n\ - float gamma = 1.0 / u_value1;\n\ - gl_FragColor = vec4( pow( color.xyz, vec3(gamma) ), color.a );\n\ - }\n"; - - LiteGraph.registerNodeType("fx/generic", LGraphFXGeneric); - global.LGraphFXGeneric = LGraphFXGeneric; - - // Vigneting ************************************ - - function LGraphFXVigneting() { - this.addInput("Tex.", "Texture"); - this.addInput("intensity", "number"); - - this.addOutput("Texture", "Texture"); - this.properties = { - intensity: 1, - invert: false, - precision: LGraphTexture.DEFAULT - }; - - if (!LGraphFXVigneting._shader) { - LGraphFXVigneting._shader = new GL.Shader( - Shader.SCREEN_VERTEX_SHADER, - LGraphFXVigneting.pixel_shader - ); - } - } - - LGraphFXVigneting.title = "Vigneting"; - LGraphFXVigneting.desc = "Vigneting"; - - LGraphFXVigneting.widgets_info = { - precision: { widget: "combo", values: LGraphTexture.MODE_VALUES } - }; - - LGraphFXVigneting.prototype.onExecute = function() { - 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 - ); - - var intensity = this.properties.intensity; - if (this.isInputConnected(1)) { - intensity = this.getInputData(1); - this.properties.intensity = intensity; - } - - gl.disable(gl.BLEND); - gl.disable(gl.DEPTH_TEST); - - var mesh = Mesh.getScreenQuad(); - var shader = LGraphFXVigneting._shader; - var invert = this.properties.invert; - - this._tex.drawTo(function() { - tex.bind(0); - shader - .uniforms({ - u_texture: 0, - u_intensity: intensity, - u_isize: [1 / tex.width, 1 / tex.height], - u_invert: invert ? 1 : 0 - }) - .draw(mesh); - }); - - this.setOutputData(0, this._tex); - }; - - LGraphFXVigneting.pixel_shader = - "precision highp float;\n\ - precision highp float;\n\ - varying vec2 v_coord;\n\ - uniform sampler2D u_texture;\n\ - uniform float u_intensity;\n\ - uniform int u_invert;\n\ - \n\ - void main() {\n\ - float luminance = 1.0 - length( v_coord - vec2(0.5) ) * 1.414;\n\ - vec4 color = texture2D(u_texture, v_coord);\n\ - if(u_invert == 1)\n\ - luminance = 1.0 - luminance;\n\ - luminance = mix(1.0, luminance, u_intensity);\n\ - gl_FragColor = vec4( luminance * color.xyz, color.a);\n\ - }\n\ - "; - - LiteGraph.registerNodeType("fx/vigneting", LGraphFXVigneting); - global.LGraphFXVigneting = LGraphFXVigneting; - } -})(this); - -(function(global) { - var LiteGraph = global.LiteGraph; - var MIDI_COLOR = "#243"; - - function MIDIEvent(data) { - this.channel = 0; - this.cmd = 0; - this.data = new Uint32Array(3); - - if (data) { - this.setup(data); - } - } - - LiteGraph.MIDIEvent = MIDIEvent; - - MIDIEvent.prototype.fromJSON = function(o) { - this.setup(o.data); - }; - - MIDIEvent.prototype.setup = function(data) { - var raw_data = data; - if (data.constructor === Object) { - raw_data = data.data; - } - - this.data.set(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#" - ]; - MIDIEvent.note_to_index = { - A: 0, - "A#": 1, - B: 2, - C: 3, - "C#": 4, - D: 5, - "D#": 6, - E: 7, - F: 8, - "F#": 9, - G: 10, - "G#": 11 - }; - - Object.defineProperty(MIDIEvent.prototype, "note", { - get: function() { - if (this.cmd != MIDIEvent.NOTEON) { - return -1; - } - return MIDIEvent.toNoteString(this.data[1], true); - }, - set: function(v) { - throw "notes cannot be assigned this way, must modify the data[1]"; - }, - enumerable: true - }); - - Object.defineProperty(MIDIEvent.prototype, "octave", { - get: function() { - if (this.cmd != MIDIEvent.NOTEON) { - return -1; - } - var octave = this.data[1] - 24; - return Math.floor(octave / 12 + 1); - }, - set: function(v) { - throw "octave cannot be assigned this way, must modify the data[1]"; - }, - enumerable: true - }); - - //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 - } - }; - - //transform from a pitch number to string like "C4" - MIDIEvent.toNoteString = function(d, skip_octave) { - d = Math.round(d); //in case it has decimals - var note = d - 21; - var octave = Math.floor((d - 24) / 12 + 1); - note = note % 12; - if (note < 0) { - note = 12 + note; - } - return MIDIEvent.notes[note] + (skip_octave ? "" : octave); - }; - - MIDIEvent.NoteStringToPitch = function(str) { - str = str.toUpperCase(); - var note = str[0]; - var octave = 4; - - if (str[1] == "#") { - note += "#"; - if (str.length > 2) { - octave = Number(str[2]); - } - } else { - if (str.length > 1) { - octave = Number(str[1]); - } - } - var pitch = MIDIEvent.note_to_index[note]; - if (pitch == null) { - return null; - } - return (octave - 1) * 12 + pitch + 21; - }; - - 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.prototype.toJSON = function() { - return { - data: [this.data[0], this.data[1], this.data[2]], - object_class: "MIDIEvent" - }; - }; - - 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" - }; - - MIDIEvent.commands_short = { - 0x80: "NOTEOFF", - 0x90: "NOTEOFF", - 0xa0: "KEYP", - 0xb0: "CC", - 0xc0: "PC", - 0xd0: "CP", - 0xe0: "PB", - 0xf0: "SYS", - 0xf2: "POS", - 0xf3: "SELECT", - 0xf6: "TUNEREQ", - 0xf8: "TT", - 0xfa: "START", - 0xfb: "CONTINUE", - 0xfc: "STOP", - 0xfe: "SENS", - 0xff: "RESET" - }; - - MIDIEvent.commands_reversed = {}; - for (var i in MIDIEvent.commands) { - MIDIEvent.commands_reversed[MIDIEvent.commands[i]] = i; - } - - //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; - this.boxcolor = "#AAA"; - this._last_time = 0; - - 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.color = MIDI_COLOR; - - 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.boxcolor = "#AFA"; - this._last_time = LiteGraph.getTime(); - 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.onDrawBackground = function(ctx) { - this.boxcolor = "#AAA"; - if (!this.flags.collapsed && this._last_midi_event) { - ctx.fillStyle = "white"; - var now = LiteGraph.getTime(); - var f = 1.0 - Math.max(0, (now - this._last_time) * 0.001); - if (f > 0) { - var t = ctx.globalAlpha; - ctx.globalAlpha *= f; - ctx.font = "12px Tahoma"; - ctx.fillText( - this._last_midi_event.toString(), - 2, - this.size[1] * 0.5 + 3 - ); - //ctx.fillRect(0,0,this.size[0],this.size[1]); - ctx.globalAlpha = t; - } - } - }; - - 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.color = MIDI_COLOR; - - 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.color = MIDI_COLOR; - - LGMIDIShow.prototype.getTitle = function() { - if (this.flags.collapsed) { - return this._str; - } - return this.title; - }; - - 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 || this.flags.collapsed) { - 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 - }; - - var that = this; - this._learning = false; - this.addWidget("button", "Learn", "", function() { - that._learning = true; - that.boxcolor = "#FA3"; - }); - - this.addInput("in", LiteGraph.EVENT); - this.addOutput("on_midi", LiteGraph.EVENT); - this.boxcolor = "#AAA"; - } - - LGMIDIFilter.title = "MIDI Filter"; - LGMIDIFilter.desc = "Filters MIDI messages"; - LGMIDIFilter.color = MIDI_COLOR; - - LGMIDIFilter["@cmd"] = { - type: "enum", - title: "Command", - values: MIDIEvent.commands_reversed - }; - - LGMIDIFilter.prototype.getTitle = function() { - var str = null; - if (this.properties.cmd == -1) { - str = "Nothing"; - } else { - str = MIDIEvent.commands_short[this.properties.cmd] || "Unknown"; - } - - if ( - this.properties.min_value != -1 && - this.properties.max_value != -1 - ) { - str += - " " + - (this.properties.min_value == this.properties.max_value - ? this.properties.max_value - : this.properties.min_value + - ".." + - this.properties.max_value); - } - - return "Filter: " + str; - }; - - LGMIDIFilter.prototype.onPropertyChanged = function(name, value) { - if (name == "cmd") { - var num = Number(value); - if (isNaN(num)) { - num = MIDIEvent.commands[value] || 0; - } - this.properties.cmd = num; - } - }; - - LGMIDIFilter.prototype.onAction = function(event, midi_event) { - if (!midi_event || midi_event.constructor !== MIDIEvent) { - return; - } - - if (this._learning) { - this._learning = false; - this.boxcolor = "#AAA"; - this.properties.channel = midi_event.channel; - this.properties.cmd = midi_event.cmd; - this.properties.min_value = this.properties.max_value = - midi_event.data[1]; - } else { - 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: 144, //0x90 - value1: 1, - value2: 1 - }; - - this.addInput("send", LiteGraph.EVENT); - this.addInput("assign", LiteGraph.EVENT); - this.addOutput("on_midi", LiteGraph.EVENT); - - this.midi_event = new MIDIEvent(); - this.gate = false; - } - - LGMIDIEvent.title = "MIDIEvent"; - LGMIDIEvent.desc = "Create a MIDI Event"; - LGMIDIEvent.color = MIDI_COLOR; - - 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]; - if (midi_event.cmd == MIDIEvent.NOTEON) { - this.gate = true; - } else if (midi_event.cmd == MIDIEvent.NOTEOFF) { - this.gate = false; - } - return; - } - - //send - var midi_event = this.midi_event; - 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.inputs) { - for (var i = 0; i < this.inputs.length; ++i) { - var input = this.inputs[i]; - if (input.link == -1) { - continue; - } - switch (input.name) { - case "note": - var v = this.getInputData(i); - if (v != null) { - if (v.constructor === String) { - v = MIDIEvent.NoteStringToPitch(v); - } - this.properties.value1 = (v | 0) % 255; - } - break; - } - } - } - - 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; - case "gate": - v = this.gate; - 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.onGetInputs = function() { - return [["note", "number"]]; - }; - - LGMIDIEvent.prototype.onGetOutputs = function() { - return [ - ["midi", "midi"], - ["on_midi", LiteGraph.EVENT], - ["command", "number"], - ["note", "number"], - ["velocity", "number"], - ["cc", "number"], - ["cc_value", "number"], - ["pitch", "number"], - ["gate", "bool"], - ["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.color = MIDI_COLOR; - - 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 LGMIDIGenerator() { - this.addInput("generate", LiteGraph.ACTION); - this.addInput("scale", "string"); - this.addInput("octave", "number"); - this.addOutput("note", LiteGraph.EVENT); - this.properties = { - notes: "A,A#,B,C,C#,D,D#,E,F,F#,G,G#", - octave: 2, - duration: 0.5, - mode: "sequence" - }; - - this.notes_pitches = LGMIDIGenerator.processScale( - this.properties.notes - ); - this.sequence_index = 0; - } - - LGMIDIGenerator.title = "MIDI Generator"; - LGMIDIGenerator.desc = "Generates a random MIDI note"; - LGMIDIGenerator.color = MIDI_COLOR; - - LGMIDIGenerator.processScale = function(scale) { - var notes = scale.split(","); - for (var i = 0; i < notes.length; ++i) { - var n = notes[i]; - if ((n.length == 2 && n[1] != "#") || n.length > 2) { - notes[i] = -LiteGraph.MIDIEvent.NoteStringToPitch(n); - } else { - notes[i] = MIDIEvent.note_to_index[n] || 0; - } - } - return notes; - }; - - LGMIDIGenerator.prototype.onPropertyChanged = function(name, value) { - if (name == "notes") { - this.notes_pitches = LGMIDIGenerator.processScale(value); - } - }; - - LGMIDIGenerator.prototype.onExecute = function() { - var octave = this.getInputData(2); - if (octave != null) { - this.properties.octave = octave; - } - - var scale = this.getInputData(1); - if (scale) { - this.notes_pitches = LGMIDIGenerator.processScale(scale); - } - }; - - LGMIDIGenerator.prototype.onAction = function(event, midi_event) { - //var range = this.properties.max - this.properties.min; - //var pitch = this.properties.min + ((Math.random() * range)|0); - var pitch = 0; - var range = this.notes_pitches.length; - var index = 0; - - if (this.properties.mode == "sequence") { - index = this.sequence_index = (this.sequence_index + 1) % range; - } else if (this.properties.mode == "random") { - index = Math.floor(Math.random() * range); - } - - var note = this.notes_pitches[index]; - if (note >= 0) { - pitch = note + (this.properties.octave - 1) * 12 + 33; - } else { - pitch = -note; - } - - var midi_event = new MIDIEvent(); - midi_event.setup([MIDIEvent.NOTEON, pitch, 10]); - var duration = this.properties.duration || 1; - this.trigger("note", midi_event); - - //noteoff - setTimeout( - function() { - var midi_event = new MIDIEvent(); - midi_event.setup([MIDIEvent.NOTEOFF, pitch, 0]); - this.trigger("note", midi_event); - }.bind(this), - duration * 1000 - ); - }; - - LiteGraph.registerNodeType("midi/generator", LGMIDIGenerator); - - function LGMIDITranspose() { - this.properties = { - amount: 0 - }; - this.addInput("in", LiteGraph.ACTION); - this.addInput("amount", "number"); - this.addOutput("out", LiteGraph.EVENT); - - this.midi_event = new MIDIEvent(); - } - - LGMIDITranspose.title = "MIDI Transpose"; - LGMIDITranspose.desc = "Transpose a MIDI note"; - LGMIDITranspose.color = MIDI_COLOR; - - LGMIDITranspose.prototype.onAction = function(event, midi_event) { - if (!midi_event || midi_event.constructor !== MIDIEvent) { - return; - } - - if ( - midi_event.data[0] == MIDIEvent.NOTEON || - midi_event.data[0] == MIDIEvent.NOTEOFF - ) { - this.midi_event = new MIDIEvent(); - this.midi_event.setup(midi_event.data); - this.midi_event.data[1] = Math.round( - this.midi_event.data[1] + this.properties.amount - ); - this.trigger("out", this.midi_event); - } else { - this.trigger("out", midi_event); - } - }; - - LGMIDITranspose.prototype.onExecute = function() { - var amount = this.getInputData(1); - if (amount != null) { - this.properties.amount = amount; - } - }; - - LiteGraph.registerNodeType("midi/transpose", LGMIDITranspose); - - function LGMIDIQuantize() { - this.properties = { - scale: "A,A#,B,C,C#,D,D#,E,F,F#,G,G#" - }; - this.addInput("note", LiteGraph.ACTION); - this.addInput("scale", "string"); - this.addOutput("out", LiteGraph.EVENT); - - this.valid_notes = new Array(12); - this.offset_notes = new Array(12); - this.processScale(this.properties.scale); - } - - LGMIDIQuantize.title = "MIDI Quantize Pitch"; - LGMIDIQuantize.desc = "Transpose a MIDI note tp fit an scale"; - LGMIDIQuantize.color = MIDI_COLOR; - - LGMIDIQuantize.prototype.onPropertyChanged = function(name, value) { - if (name == "scale") { - this.processScale(value); - } - }; - - LGMIDIQuantize.prototype.processScale = function(scale) { - this._current_scale = scale; - this.notes_pitches = LGMIDIGenerator.processScale(scale); - for (var i = 0; i < 12; ++i) { - this.valid_notes[i] = this.notes_pitches.indexOf(i) != -1; - } - for (var i = 0; i < 12; ++i) { - if (this.valid_notes[i]) { - this.offset_notes[i] = 0; - continue; - } - for (var j = 1; j < 12; ++j) { - if (this.valid_notes[(i - j) % 12]) { - this.offset_notes[i] = -j; - break; - } - if (this.valid_notes[(i + j) % 12]) { - this.offset_notes[i] = j; - break; - } - } - } - }; - - LGMIDIQuantize.prototype.onAction = function(event, midi_event) { - if (!midi_event || midi_event.constructor !== MIDIEvent) { - return; - } - - if ( - midi_event.data[0] == MIDIEvent.NOTEON || - midi_event.data[0] == MIDIEvent.NOTEOFF - ) { - this.midi_event = new MIDIEvent(); - this.midi_event.setup(midi_event.data); - var note = midi_event.note; - var index = MIDIEvent.note_to_index[note]; - var offset = this.offset_notes[index]; - this.midi_event.data[1] += offset; - this.trigger("out", this.midi_event); - } else { - this.trigger("out", midi_event); - } - }; - - LGMIDIQuantize.prototype.onExecute = function() { - var scale = this.getInputData(1); - if (scale != null && scale != this._current_scale) { - this.processScale(scale); - } - }; - - LiteGraph.registerNodeType("midi/quantize", LGMIDIQuantize); - - function LGMIDIPlay() { - this.properties = { - volume: 0.5, - duration: 1 - }; - this.addInput("note", LiteGraph.ACTION); - this.addInput("volume", "number"); - this.addInput("duration", "number"); - this.addOutput("note", LiteGraph.EVENT); - - if (typeof AudioSynth == "undefined") { - console.error( - "Audiosynth.js not included, LGMidiPlay requires that library" - ); - this.boxcolor = "red"; - } else { - var Synth = (this.synth = new AudioSynth()); - this.instrument = Synth.createInstrument("piano"); - } - } - - LGMIDIPlay.title = "MIDI Play"; - LGMIDIPlay.desc = "Plays a MIDI note"; - LGMIDIPlay.color = MIDI_COLOR; - - LGMIDIPlay.prototype.onAction = function(event, midi_event) { - if (!midi_event || midi_event.constructor !== MIDIEvent) { - return; - } - - if (this.instrument && midi_event.data[0] == MIDIEvent.NOTEON) { - var note = midi_event.note; //C# - if (!note || note == "undefined" || note.constructor !== String) { - return; - } - this.instrument.play( - note, - midi_event.octave, - this.properties.duration, - this.properties.volume - ); - } - this.trigger("note", midi_event); - }; - - LGMIDIPlay.prototype.onExecute = function() { - var volume = this.getInputData(1); - if (volume != null) { - this.properties.volume = volume; - } - - var duration = this.getInputData(2); - if (duration != null) { - this.properties.duration = duration; - } - }; - - LiteGraph.registerNodeType("midi/play", LGMIDIPlay); - - function LGMIDIKeys() { - this.properties = { - num_octaves: 2, - start_octave: 2 - }; - this.addInput("note", LiteGraph.ACTION); - this.addInput("reset", LiteGraph.ACTION); - this.addOutput("note", LiteGraph.EVENT); - this.size = [400, 100]; - this.keys = []; - this._last_key = -1; - } - - LGMIDIKeys.title = "MIDI Keys"; - LGMIDIKeys.desc = "Keyboard to play notes"; - LGMIDIKeys.color = MIDI_COLOR; - - LGMIDIKeys.keys = [ - { x: 0, w: 1, h: 1, t: 0 }, - { x: 0.75, w: 0.5, h: 0.6, t: 1 }, - { x: 1, w: 1, h: 1, t: 0 }, - { x: 1.75, w: 0.5, h: 0.6, t: 1 }, - { x: 2, w: 1, h: 1, t: 0 }, - { x: 2.75, w: 0.5, h: 0.6, t: 1 }, - { x: 3, w: 1, h: 1, t: 0 }, - { x: 4, w: 1, h: 1, t: 0 }, - { x: 4.75, w: 0.5, h: 0.6, t: 1 }, - { x: 5, w: 1, h: 1, t: 0 }, - { x: 5.75, w: 0.5, h: 0.6, t: 1 }, - { x: 6, w: 1, h: 1, t: 0 } - ]; - - LGMIDIKeys.prototype.onDrawForeground = function(ctx) { - if (this.flags.collapsed) { - return; - } - - var num_keys = this.properties.num_octaves * 12; - this.keys.length = num_keys; - var key_width = this.size[0] / (this.properties.num_octaves * 7); - var key_height = this.size[1]; - - ctx.globalAlpha = 1; - - for ( - var k = 0; - k < 2; - k++ //draw first whites (0) then blacks (1) - ) { - for (var i = 0; i < num_keys; ++i) { - var key_info = LGMIDIKeys.keys[i % 12]; - if (key_info.t != k) { - continue; - } - var octave = Math.floor(i / 12); - var x = octave * 7 * key_width + key_info.x * key_width; - if (k == 0) { - ctx.fillStyle = this.keys[i] ? "#CCC" : "white"; - } else { - ctx.fillStyle = this.keys[i] ? "#333" : "black"; - } - ctx.fillRect( - x + 1, - 0, - key_width * key_info.w - 2, - key_height * key_info.h - ); - } - } - }; - - LGMIDIKeys.prototype.getKeyIndex = function(pos) { - var num_keys = this.properties.num_octaves * 12; - var key_width = this.size[0] / (this.properties.num_octaves * 7); - var key_height = this.size[1]; - - for ( - var k = 1; - k >= 0; - k-- //test blacks first (1) then whites (0) - ) { - for (var i = 0; i < this.keys.length; ++i) { - var key_info = LGMIDIKeys.keys[i % 12]; - if (key_info.t != k) { - continue; - } - var octave = Math.floor(i / 12); - var x = octave * 7 * key_width + key_info.x * key_width; - var w = key_width * key_info.w; - var h = key_height * key_info.h; - if (pos[0] < x || pos[0] > x + w || pos[1] > h) { - continue; - } - return i; - } - } - return -1; - }; - - LGMIDIKeys.prototype.onAction = function(event, params) { - if (event == "reset") { - for (var i = 0; i < this.keys.length; ++i) { - this.keys[i] = false; - } - return; - } - - if (!params || params.constructor !== MIDIEvent) { - return; - } - var midi_event = params; - var start_note = (this.properties.start_octave - 1) * 12 + 29; - var index = midi_event.data[1] - start_note; - if (index >= 0 && index < this.keys.length) { - if (midi_event.data[0] == MIDIEvent.NOTEON) { - this.keys[index] = true; - } else if (midi_event.data[0] == MIDIEvent.NOTEOFF) { - this.keys[index] = false; - } - } - - this.trigger("note", midi_event); - }; - - LGMIDIKeys.prototype.onMouseDown = function(e, pos) { - if (pos[1] < 0) { - return; - } - var index = this.getKeyIndex(pos); - this.keys[index] = true; - this._last_key = index; - var pitch = (this.properties.start_octave - 1) * 12 + 29 + index; - var midi_event = new MIDIEvent(); - midi_event.setup([MIDIEvent.NOTEON, pitch, 100]); - this.trigger("note", midi_event); - return true; - }; - - LGMIDIKeys.prototype.onMouseMove = function(e, pos) { - if (pos[1] < 0 || this._last_key == -1) { - return; - } - this.setDirtyCanvas(true); - var index = this.getKeyIndex(pos); - if (this._last_key == index) { - return true; - } - this.keys[this._last_key] = false; - var pitch = - (this.properties.start_octave - 1) * 12 + 29 + this._last_key; - var midi_event = new MIDIEvent(); - midi_event.setup([MIDIEvent.NOTEOFF, pitch, 100]); - this.trigger("note", midi_event); - - this.keys[index] = true; - var pitch = (this.properties.start_octave - 1) * 12 + 29 + index; - var midi_event = new MIDIEvent(); - midi_event.setup([MIDIEvent.NOTEON, pitch, 100]); - this.trigger("note", midi_event); - - this._last_key = index; - return true; - }; - - LGMIDIKeys.prototype.onMouseUp = function(e, pos) { - if (pos[1] < 0) { - return; - } - var index = this.getKeyIndex(pos); - this.keys[index] = false; - this._last_key = -1; - var pitch = (this.properties.start_octave - 1) * 12 + 29 + index; - var midi_event = new MIDIEvent(); - midi_event.setup([MIDIEvent.NOTEOFF, pitch, 100]); - this.trigger("note", midi_event); - return true; - }; - - LiteGraph.registerNodeType("midi/keys", LGMIDIKeys); - - 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 LGAudioMediaSource() { - this.properties = { - gain: 0.5 - }; - - this._audionodes = []; - this._media_stream = null; - - this.addOutput("out", "audio"); - this.addInput("gain", "number"); - - //create gain node to control volume - var context = LGAudio.getAudioContext(); - this.audionode = context.createGain(); - this.audionode.graphnode = this; - this.audionode.gain.value = this.properties.gain; - } - - LGAudioMediaSource.prototype.onAdded = function(graph) { - if (graph.status === LGraph.STATUS_RUNNING) { - this.onStart(); - } - }; - - LGAudioMediaSource.prototype.onStart = function() { - if (this._media_stream == null && !this._waiting_confirmation) { - this.openStream(); - } - }; - - LGAudioMediaSource.prototype.onStop = function() { - this.audionode.gain.value = 0; - }; - - LGAudioMediaSource.prototype.onPause = function() { - this.audionode.gain.value = 0; - }; - - LGAudioMediaSource.prototype.onUnpause = function() { - this.audionode.gain.value = this.properties.gain; - }; - - LGAudioMediaSource.prototype.onRemoved = function() { - this.audionode.gain.value = 0; - if (this.audiosource_node) { - this.audiosource_node.disconnect(this.audionode); - this.audiosource_node = null; - } - if (this._media_stream) { - var tracks = this._media_stream.getTracks(); - if (tracks.length) { - tracks[0].stop(); - } - } - }; - - LGAudioMediaSource.prototype.openStream = function() { - if (!navigator.mediaDevices) { - console.log( - "getUserMedia() is not supported in your browser, use chrome and enable WebRTC from about://flags" - ); - return; - } - - this._waiting_confirmation = true; - - // Not showing vendor prefixes. - navigator.mediaDevices - .getUserMedia({ audio: true, video: false }) - .then(this.streamReady.bind(this)) - .catch(onFailSoHard); - - var that = this; - function onFailSoHard(err) { - console.log("Media rejected", err); - that._media_stream = false; - that.boxcolor = "red"; - } - }; - - LGAudioMediaSource.prototype.streamReady = function(localMediaStream) { - this._media_stream = localMediaStream; - //this._waiting_confirmation = false; - - //init context - if (this.audiosource_node) { - this.audiosource_node.disconnect(this.audionode); - } - var context = LGAudio.getAudioContext(); - this.audiosource_node = context.createMediaStreamSource( - localMediaStream - ); - this.audiosource_node.graphnode = this; - this.audiosource_node.connect(this.audionode); - this.boxcolor = "white"; - }; - - LGAudioMediaSource.prototype.onExecute = function() { - if (this._media_stream == null && !this._waiting_confirmation) { - this.openStream(); - } - - 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 = this.properties.gain = v; - } - } - } - }; - - LGAudioMediaSource.prototype.onAction = function(event) { - if (event == "Play") { - this.audionode.gain.value = this.properties.gain; - } else if (event == "Stop") { - this.audionode.gain.value = 0; - } - }; - - LGAudioMediaSource.prototype.onPropertyChanged = function(name, value) { - if (name == "gain") { - this.audionode.gain.value = value; - } - }; - - //Helps connect/disconnect AudioNodes when new connections are made in the node - LGAudioMediaSource.prototype.onConnectionsChange = - LGAudio.onConnectionsChange; - - LGAudioMediaSource.prototype.onGetInputs = function() { - return [ - ["playbackRate", "number"], - ["Play", LiteGraph.ACTION], - ["Stop", LiteGraph.ACTION] - ]; - }; - - LGAudioMediaSource.title = "MediaSource"; - LGAudioMediaSource.desc = "Plays microphone"; - LiteGraph.registerNodeType("audio/media_source", LGAudioMediaSource); - - //***************************************************** - - 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 LGAudioADSR() { - //default - this.properties = { - A: 0.1, - D: 0.1, - S: 0.1, - R: 0.1 - }; - - this.audionode = LGAudio.getAudioContext().createGain(); - this.audionode.gain.value = 0; - this.addInput("in", "audio"); - this.addInput("gate", "bool"); - this.addOutput("out", "audio"); - this.gate = false; - } - - LGAudioADSR.prototype.onExecute = function() { - var audioContext = LGAudio.getAudioContext(); - var now = audioContext.currentTime; - var node = this.audionode; - var gain = node.gain; - var current_gate = this.getInputData(1); - - var A = this.getInputOrProperty("A"); - var D = this.getInputOrProperty("D"); - var S = this.getInputOrProperty("S"); - var R = this.getInputOrProperty("R"); - - if (!this.gate && current_gate) { - gain.cancelScheduledValues(0); - gain.setValueAtTime(0, now); - gain.linearRampToValueAtTime(1, now + A); - gain.linearRampToValueAtTime(S, now + A + D); - } else if (this.gate && !current_gate) { - gain.cancelScheduledValues(0); - gain.setValueAtTime(gain.value, now); - gain.linearRampToValueAtTime(0, now + R); - } - - this.gate = current_gate; - }; - - LGAudioADSR.prototype.onGetInputs = function() { - return [ - ["A", "number"], - ["D", "number"], - ["S", "number"], - ["R", "number"] - ]; - }; - - LGAudio.createAudioNodeWrapper(LGAudioADSR); - - LGAudioADSR.title = "ADSR"; - LGAudioADSR.desc = "Audio envelope"; - LiteGraph.registerNodeType("audio/adsr", LGAudioADSR); - - 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; - try { - this.audionode.start(); - } catch (err) {} - } - }; - - 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, - only_send_changes: true - }; - this._ws = null; - this._last_sent_data = []; - this._last_received_data = []; - } - - LGWebSocket.title = "WebSocket"; - LGWebSocket.desc = "Send data through a websocket"; - - LGWebSocket.prototype.onPropertyChanged = function(name, value) { - if (name == "url") { - this.connectSocket(); - } - }; - - LGWebSocket.prototype.onExecute = function() { - if (!this._ws && this.properties.url) { - this.connectSocket(); - } - - if (!this._ws || this._ws.readyState != WebSocket.OPEN) { - return; - } - - var room = this.properties.room; - var only_changes = this.properties.only_send_changes; - - for (var i = 1; i < this.inputs.length; ++i) { - var data = this.getInputData(i); - if (data == null) { - continue; - } - var json; - try { - json = JSON.stringify({ - type: 0, - room: room, - channel: i, - data: data - }); - } catch (err) { - continue; - } - if (only_changes && this._last_sent_data[i] == json) { - continue; - } - - this._last_sent_data[i] = json; - this._ws.send(json); - } - - for (var i = 1; i < this.outputs.length; ++i) { - this.setOutputData(i, this._last_received_data[i]); - } - - if (this.boxcolor == "#AFA") { - this.boxcolor = "#6C6"; - } - }; - - LGWebSocket.prototype.connectSocket = 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 = "#6C6"; - }; - this._ws.onmessage = function(e) { - that.boxcolor = "#AFA"; - var data = JSON.parse(e.data); - if (data.room && data.room != this.properties.room) { - return; - } - if (e.data.type == 1) { - if ( - data.data.object_class && - LiteGraph[data.data.object_class] - ) { - var obj = null; - try { - obj = new LiteGraph[data.data.object_class](data.data); - that.triggerSlot(0, obj); - } catch (err) { - return; - } - } else { - that.triggerSlot(0, data.data); - } - } else { - that._last_received_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.room_widget = this.addWidget( - "text", - "Room", - "lgraph", - this.setRoom.bind(this) - ); - this.addWidget( - "button", - "Reconnect", - null, - this.connectSocket.bind(this) - ); - - 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", - only_send_changes: true - }; - - this._server = null; - this.connectSocket(); - this._last_sent_data = []; - this._last_received_data = []; - - if(typeof(SillyClient) == "undefined") - console.warn("remember to add SillyClient.js to your project: https://tamats.com/projects/sillyserver/src/sillyclient.js"); - } - - LGSillyClient.title = "SillyClient"; - LGSillyClient.desc = "Connects to SillyServer to broadcast messages"; - - LGSillyClient.prototype.onPropertyChanged = function(name, value) { - if (name == "room") { - this.room_widget.value = value; - } - this.connectSocket(); - }; - - LGSillyClient.prototype.setRoom = function(room_name) { - this.properties.room = room_name; - this.room_widget.value = room_name; - this.connectSocket(); - }; - - //force label names - LGSillyClient.prototype.onDrawForeground = function() { - for (var i = 1; i < this.inputs.length; ++i) { - var slot = this.inputs[i]; - slot.label = "in_" + i; - } - for (var i = 1; i < this.outputs.length; ++i) { - var slot = this.outputs[i]; - slot.label = "out_" + i; - } - }; - - LGSillyClient.prototype.onExecute = function() { - if (!this._server || !this._server.is_connected) { - return; - } - - var only_send_changes = this.properties.only_send_changes; - - for (var i = 1; i < this.inputs.length; ++i) { - var data = this.getInputData(i); - var prev_data = this._last_sent_data[i]; - if (data != null) { - if (only_send_changes) - { - var is_equal = true; - if( data && data.length && prev_data && prev_data.length == data.length && data.constructor !== String) - { - for(var j = 0; j < data.length; ++j) - if( prev_data[j] != data[j] ) - { - is_equal = false; - break; - } - } - else if(this._last_sent_data[i] != data) - is_equal = false; - if(is_equal) - continue; - } - this._server.sendMessage({ type: 0, channel: i, data: data }); - if( data.length && data.constructor !== String ) - { - if( this._last_sent_data[i] ) - { - this._last_sent_data[i].length = data.length; - for(var j = 0; j < data.length; ++j) - this._last_sent_data[i][j] = data[j]; - } - else //create - { - if(data.constructor === Array) - this._last_sent_data[i] = data.concat(); - else - this._last_sent_data[i] = new data.constructor( data ); - } - } - else - this._last_sent_data[i] = data; //should be cloned - } - } - - for (var i = 1; i < this.outputs.length; ++i) { - this.setOutputData(i, this._last_received_data[i]); - } - - if (this.boxcolor == "#AFA") { - this.boxcolor = "#6C6"; - } - }; - - LGSillyClient.prototype.connectSocket = 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 = "#6C6"; - }; - this._server.on_message = function(id, msg) { - var data = null; - try { - data = JSON.parse(msg); - } catch (err) { - return; - } - - if (data.type == 1) { - //EVENT slot - if ( - data.data.object_class && - LiteGraph[data.data.object_class] - ) { - var obj = null; - try { - obj = new LiteGraph[data.data.object_class](data.data); - that.triggerSlot(0, obj); - } catch (err) { - return; - } - } else { - that.triggerSlot(0, data.data); - } - } //for FLOW slots - else { - that._last_received_data[data.channel || 0] = data.data; - } - that.boxcolor = "#AFA"; - }; - 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) { - try { - this._server.connect(this.properties.url, this.properties.room); - } catch (err) { - console.error("SillyServer error: " + err); - this._server = null; - return; - } - 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 deleted file mode 100644 index e45402843..000000000 --- a/build/litegraph.min.js +++ /dev/null @@ -1,10005 +0,0 @@ -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(v, c, k) { - v != Array.prototype && v != Object.prototype && (v[c] = k.value); -}; -$jscomp.getGlobal = function(v) { - return "undefined" != typeof window && window === v ? v : "undefined" != typeof global && null != global ? global : v; -}; -$jscomp.global = $jscomp.getGlobal(this); -$jscomp.polyfill = function(v, c, k, h) { - if (c) { - k = $jscomp.global; - v = v.split("."); - for (h = 0; h < v.length - 1; h++) { - var m = v[h]; - m in k || (k[m] = {}); - k = k[m]; - } - v = v[v.length - 1]; - h = k[v]; - c = c(h); - c != h && null != c && $jscomp.defineProperty(k, v, {configurable:!0, writable:!0, value:c}); - } -}; -$jscomp.polyfill("Array.prototype.fill", function(v) { - return v ? v : function(c, k, h) { - var m = this.length || 0; - 0 > k && (k = Math.max(0, m + k)); - if (null == h || h > m) { - h = m; - } - h = Number(h); - 0 > h && (h = Math.max(0, m + h)); - for (k = Number(k || 0); k < h; k++) { - this[k] = c; - } - 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 v = 0; - return function(c) { - return $jscomp.SYMBOL_PREFIX + (c || "") + v++; - }; -}(); -$jscomp.initSymbolIterator = function() { - $jscomp.initSymbol(); - var v = $jscomp.global.Symbol.iterator; - v || (v = $jscomp.global.Symbol.iterator = $jscomp.global.Symbol("iterator")); - "function" != typeof Array.prototype[v] && $jscomp.defineProperty(Array.prototype, v, {configurable:!0, writable:!0, value:function() { - return $jscomp.arrayIterator(this); - }}); - $jscomp.initSymbolIterator = function() { - }; -}; -$jscomp.arrayIterator = function(v) { - var c = 0; - return $jscomp.iteratorPrototype(function() { - return c < v.length ? {done:!1, value:v[c++]} : {done:!0}; - }); -}; -$jscomp.iteratorPrototype = function(v) { - $jscomp.initSymbolIterator(); - v = {next:v}; - v[$jscomp.global.Symbol.iterator] = function() { - return this; - }; - return v; -}; -$jscomp.iteratorFromArray = function(v, c) { - $jscomp.initSymbolIterator(); - v instanceof String && (v += ""); - var k = 0, h = {next:function() { - if (k < v.length) { - var m = k++; - return {value:c(m, v[m]), done:!1}; - } - h.next = function() { - return {done:!0, value:void 0}; - }; - return h.next(); - }}; - h[Symbol.iterator] = function() { - return h; - }; - return h; -}; -$jscomp.polyfill("Array.prototype.values", function(v) { - return v ? v : function() { - return $jscomp.iteratorFromArray(this, function(c, k) { - return k; - }); - }; -}, "es8", "es3"); -$jscomp.polyfill("Array.prototype.keys", function(v) { - return v ? v : function() { - return $jscomp.iteratorFromArray(this, function(c) { - return c; - }); - }; -}, "es6", "es3"); -(function(v) { - function c(a) { - e.debug && console.log("Graph created"); - this.list_of_graphcanvas = null; - this.clear(); - a && this.configure(a); - } - function k(a, b, d, q, n, e) { - this.id = a; - this.type = b; - this.origin_id = d; - this.origin_slot = q; - this.target_id = n; - this.target_slot = e; - this._data = null; - this._pos = new Float32Array(2); - } - function h(a) { - this._ctor(a); - } - function m(a) { - this._ctor(a); - } - function t(a, b) { - this.offset = new Float32Array([0, 0]); - this.scale = 1; - this.max_scale = 10; - this.min_scale = 0.1; - this.onredraw = null; - this.enabled = !0; - this.last_mouse = [0, 0]; - this.element = null; - this.visible_area = new Float32Array(4); - a && (this.element = a, b || this.bindEvents(a)); - } - function g(a, b, d) { - d = d || {}; - 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.ds = new t; - this.zoom_modify_alpha = !0; - this.title_text_font = "" + e.NODE_TEXT_SIZE + "px Arial"; - this.inner_text_font = "normal " + e.NODE_SUBTEXT_SIZE + "px Arial"; - this.node_title_color = e.NODE_TITLE_COLOR; - this.default_link_color = e.LINK_COLOR; - this.default_connection_color = {input_off:"#778", input_on:"#7F7", output_off:"#778", output_on:"#7F7"}; - this.highquality_render = !0; - this.use_gradients = !1; - this.editor_alpha = 1; - this.pause_rendering = !1; - this.clear_background = !0; - this.read_only = !1; - this.render_only_selected = !0; - this.live_mode = !1; - this.allow_searchbox = this.allow_interaction = this.allow_dragnodes = this.allow_dragcanvas = this.show_info = !0; - this.drag_mode = this.allow_reconnect_links = !1; - this.filter = this.dragging_rectangle = null; - this.always_render_background = !1; - this.render_canvas_border = this.render_shadows = !0; - this.render_connections_shadows = !1; - this.render_connections_border = !0; - this.render_connection_arrows = this.render_curved_connections = !1; - this.render_collapsed_slots = !0; - this.render_execution_order = !1; - this.render_link_tooltip = this.render_title_colored = !0; - this.links_render_mode = e.SPLINE_LINK; - this.canvas_mouse = [0, 0]; - this.onSelectionChange = this.onNodeMoved = this.onDrawLinkTooltip = this.onDrawOverlay = this.onDrawForeground = this.onDrawBackground = this.onMouse = this.onSearchBoxSelection = this.onSearchBox = null; - this.connections_width = 3; - this.round_radius = 8; - this.over_link_center = this.node_widget = this.current_node = null; - this.last_mouse_position = [0, 0]; - this.visible_area = this.ds.visible_area; - this.visible_links = []; - b && b.attachCanvas(this); - this.setCanvas(a); - this.clear(); - d.skip_render || this.startRendering(); - this.autoresize = d.autoresize; - } - function D(a, b) { - return Math.sqrt((b[0] - a[0]) * (b[0] - a[0]) + (b[1] - a[1]) * (b[1] - a[1])); - } - function B(a, b, d, q, n, e) { - return d < a && d + n > a && q < b && q + e > b ? !0 : !1; - } - function x(a, b) { - var d = a[0] + a[2], q = a[1] + a[3], n = b[1] + b[3]; - return a[0] > b[0] + b[2] || a[1] > n || d < b[0] || q < b[1] ? !1 : !0; - } - function E(a, b) { - function d(a) { - var d = parseInt(n.style.top); - n.style.top = (d + a.deltaY * b.scroll_speed).toFixed() + "px"; - a.preventDefault(); - return !0; - } - this.options = b = b || {}; - var q = 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 && b.event.constructor !== PointerEvent && (console.error("Event passed to ContextMenu is not of type MouseEvent or CustomEvent. Ignoring it."), b.event = null); - var n = document.createElement("div"); - n.className = "litegraph litecontextmenu litemenubar-panel"; - b.className && (n.className += " " + b.className); - n.style.minWidth = 100; - n.style.minHeight = 100; - n.style.pointerEvents = "none"; - setTimeout(function() { - n.style.pointerEvents = "auto"; - }, 100); - n.addEventListener("mouseup", function(a) { - a.preventDefault(); - return !0; - }, !0); - n.addEventListener("contextmenu", function(a) { - if (2 != a.button) { - return !1; - } - a.preventDefault(); - return !1; - }, !0); - n.addEventListener("mousedown", function(a) { - if (2 == a.button) { - return q.close(), a.preventDefault(), !0; - } - }, !0); - b.scroll_speed || (b.scroll_speed = 0.1); - n.addEventListener("wheel", d, !0); - n.addEventListener("mousewheel", d, !0); - this.root = n; - if (b.title) { - var e = document.createElement("div"); - e.className = "litemenu-title"; - e.innerHTML = b.title; - n.appendChild(e); - } - for (var f = e = 0; f < a.length; f++) { - var c = a.constructor == Array ? a[f] : f; - null != c && c.constructor !== String && (c = void 0 === c.content ? String(c) : c.content); - this.addItem(c, a[f], b); - e++; - } - n.addEventListener("mouseleave", function(a) { - q.lock || (n.closing_timer && clearTimeout(n.closing_timer), n.closing_timer = setTimeout(q.close.bind(q, a), 500)); - }); - n.addEventListener("mouseenter", function(a) { - n.closing_timer && clearTimeout(n.closing_timer); - }); - a = document; - b.event && (a = b.event.target.ownerDocument); - a || (a = document); - a.fullscreenElement ? a.fullscreenElement.appendChild(n) : a.body.appendChild(n); - e = b.left || 0; - a = b.top || 0; - b.event && (e = b.event.clientX - 10, a = b.event.clientY - 10, b.title && (a -= 20), b.parentMenu && (e = b.parentMenu.root.getBoundingClientRect(), e = e.left + e.width), f = document.body.getBoundingClientRect(), c = n.getBoundingClientRect(), e > f.width - c.width - 10 && (e = f.width - c.width - 10), a > f.height - c.height - 10 && (a = f.height - c.height - 10)); - n.style.left = e + "px"; - n.style.top = a + "px"; - b.scale && (n.style.transform = "scale(" + b.scale + ")"); - } - function A(a) { - this.points = a; - this.nearest = this.selected = -1; - this.size = null; - this.must_update = !0; - this.margin = 5; - } - var e = v.LiteGraph = {VERSION:0.4, CANVAS_GRID_SIZE:10, NODE_TITLE_HEIGHT:30, NODE_TITLE_TEXT_Y:20, NODE_SLOT_HEIGHT:20, 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:"#353535", NODE_DEFAULT_BOXCOLOR:"#666", NODE_DEFAULT_SHAPE:"box", DEFAULT_SHADOW_COLOR:"rgba(0,0,0,0.5)", DEFAULT_GROUP_FONT:24, - WIDGET_BGCOLOR:"#222", WIDGET_OUTLINE_COLOR:"#666", WIDGET_TEXT_COLOR:"#DDD", WIDGET_SECONDARY_TEXT_COLOR:"#999", LINK_COLOR:"#9A9", EVENT_LINK_COLOR:"#A86", 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, STRAIGHT_LINK:0, - LINEAR_LINK:1, SPLINE_LINK:2, NORMAL_TITLE:0, NO_TITLE:1, TRANSPARENT_TITLE:2, AUTOHIDE_TITLE:3, proxy:null, node_images_path:"", debug:!1, catch_exceptions:!0, throw_errors:!0, allow_scripts:!1, registered_node_types:{}, node_types_by_file_extension:{}, Nodes:{}, searchbox_extras:{}, registerNodeType:function(a, b) { - if (!b.prototype) { - throw "Cannot register a simple object, it must be a class with a prototype"; - } - b.type = a; - e.debug && console.log("Node registered: " + a); - a.split("/"); - var d = b.name, q = a.lastIndexOf("/"); - b.category = a.substr(0, q); - b.title || (b.title = d); - if (b.prototype) { - for (var n in h.prototype) { - b.prototype[n] || (b.prototype[n] = h.prototype[n]); - } - } - Object.hasOwnProperty(b.prototype, "shape") || Object.defineProperty(b.prototype, "shape", {set:function(a) { - switch(a) { - case "default": - delete this._shape; - break; - case "box": - this._shape = e.BOX_SHAPE; - break; - case "round": - this._shape = e.ROUND_SHAPE; - break; - case "circle": - this._shape = e.CIRCLE_SHAPE; - break; - case "card": - this._shape = e.CARD_SHAPE; - break; - default: - this._shape = a; - } - }, get:function(a) { - return this._shape; - }, enumerable:!0}); - (n = this.registered_node_types[a]) && console.log("replacing node type: " + a); - this.registered_node_types[a] = b; - b.constructor.name && (this.Nodes[d] = b); - if (e.onNodeTypeRegistered) { - e.onNodeTypeRegistered(a, b); - } - if (n && e.onNodeTypeReplaced) { - e.onNodeTypeReplaced(a, b, n); - } - 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 (n = 0; n < b.supported_extensions.length; n++) { - (a = b.supported_extensions[n]) && a.constructor === String && (this.node_types_by_file_extension[a.toLowerCase()] = b); - } - } - }, wrapFunctionAsNode:function(a, b, d, q, n) { - for (var f = Array(b.length), c = "", g = e.getParameterNames(b), l = 0; l < g.length; ++l) { - c += "this.addInput('" + g[l] + "'," + (d && d[l] ? "'" + d[l] + "'" : "0") + ");\n"; - } - c += "this.addOutput('out'," + (q ? "'" + q + "'" : 0) + ");\n"; - n && (c += "this.properties = " + JSON.stringify(n) + ";\n"); - d = Function(c); - d.title = a.split("/").pop(); - d.desc = "Generated from " + b.name; - d.prototype.onExecute = function() { - for (var a = 0; a < f.length; ++a) { - f[a] = this.getInputData(a); - } - a = b.apply(this, f); - this.setOutputData(0, a); - }; - this.registerNodeType(a, d); - }, addNodeMethod:function(a, b) { - h.prototype[a] = b; - for (var d in this.registered_node_types) { - var q = this.registered_node_types[d]; - q.prototype[a] && (q.prototype["_" + a] = q.prototype[a]); - q.prototype[a] = b; - } - }, createNode:function(a, b, d) { - var q = this.registered_node_types[a]; - if (!q) { - return e.debug && console.log('GraphNode type "' + a + '" not registered.'), null; - } - b = b || q.title || a; - var n = null; - if (e.catch_exceptions) { - try { - n = new q(b); - } catch (G) { - return console.error(G), null; - } - } else { - n = new q(b); - } - n.type = a; - !n.title && b && (n.title = b); - n.properties || (n.properties = {}); - n.properties_info || (n.properties_info = []); - n.flags || (n.flags = {}); - n.size || (n.size = n.computeSize()); - n.pos || (n.pos = e.DEFAULT_POSITION.concat()); - n.mode || (n.mode = e.ALWAYS); - if (d) { - for (var f in d) { - n[f] = d[f]; - } - } - return n; - }, getNodeType:function(a) { - return this.registered_node_types[a]; - }, getNodeTypesInCategory:function(a, b) { - var d = [], q; - for (q in this.registered_node_types) { - var e = this.registered_node_types[q]; - b && e.filter && e.filter != b || ("" == a ? null == e.category && d.push(e) : e.category == a && d.push(e)); - } - return d; - }, getNodeTypesCategories:function(a) { - var b = {"":1}, d; - for (d in this.registered_node_types) { - var e = this.registered_node_types[d]; - !e.category || e.skip_list || a && e.filter != a || (b[e.category] = 1); - } - a = []; - for (d in b) { - a.push(d); - } - return a; - }, reloadNodes:function(a) { - for (var b = document.getElementsByTagName("script"), d = [], q = 0; q < b.length; q++) { - d.push(b[q]); - } - b = document.getElementsByTagName("head")[0]; - a = document.location.href + a; - for (q = 0; q < d.length; q++) { - var n = d[q].src; - if (n && n.substr(0, a.length) == a) { - try { - e.debug && console.log("Reloading: " + n); - var f = document.createElement("script"); - f.type = "text/javascript"; - f.src = n; - b.appendChild(f); - b.removeChild(d[q]); - } catch (G) { - if (e.throw_errors) { - throw G; - } - e.debug && console.log("Error while reloading " + n); - } - } - } - e.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 d in a) { - b[d] = a[d]; - } - return b; - }, isValidConnection:function(a, b) { - if (!a || !b || a == b || a == e.EVENT && b == e.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 d = 0; d < a.length; ++d) { - for (var q = 0; q < b.length; ++q) { - if (a[d] == b[q]) { - return !0; - } - } - } - return !1; - }, registerSearchboxExtra:function(a, b, d) { - this.searchbox_extras[b.toLowerCase()] = {type:a, desc:b, data:d}; - }}; - e.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(); - }; - v.LGraph = e.LGraph = c; - c.supported_types = ["number", "string", "boolean"]; - c.prototype.getSupportedTypes = function() { - return this.supported_types || c.supported_types; - }; - c.STATUS_STOPPED = 1; - c.STATUS_RUNNING = 2; - c.prototype.clear = function() { - this.stop(); - this.status = c.STATUS_STOPPED; - this.last_link_id = this.last_node_id = 0; - this._version = -1; - if (this._nodes) { - for (var a = 0; a < this._nodes.length; ++a) { - var b = this._nodes[a]; - if (b.onRemoved) { - b.onRemoved(); - } - } - } - this._nodes = []; - this._nodes_by_id = {}; - this._nodes_in_order = []; - this._nodes_executable = null; - this._groups = []; - this.links = {}; - this.iteration = 0; - this.config = {}; - this.vars = {}; - 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.inputs = {}; - this.outputs = {}; - this.change(); - this.sendActionToCanvas("clear"); - }; - c.prototype.attachCanvas = function(a) { - if (a.constructor != g) { - throw "attachCanvas expects a LGraphCanvas instance"; - } - a.graph && a.graph != this && a.graph.detachCanvas(a); - a.graph = this; - this.list_of_graphcanvas || (this.list_of_graphcanvas = []); - this.list_of_graphcanvas.push(a); - }; - c.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)); - } - }; - c.prototype.start = function(a) { - if (this.status != c.STATUS_RUNNING) { - this.status = c.STATUS_RUNNING; - if (this.onPlayEvent) { - this.onPlayEvent(); - } - this.sendEventToAllNodes("onStart"); - this.last_update_time = this.starttime = e.getTime(); - a = a || 0; - var b = this; - if (0 == a && "undefined" != typeof window && window.requestAnimationFrame) { - var d = function() { - -1 == b.execution_timer_id && (window.requestAnimationFrame(d), b.runStep(1, !this.catch_errors)); - }; - this.execution_timer_id = -1; - d(); - } else { - this.execution_timer_id = setInterval(function() { - b.runStep(1, !this.catch_errors); - }, a); - } - } - }; - c.prototype.stop = function() { - if (this.status != c.STATUS_STOPPED) { - this.status = c.STATUS_STOPPED; - if (this.onStopEvent) { - this.onStopEvent(); - } - null != this.execution_timer_id && (-1 != this.execution_timer_id && clearInterval(this.execution_timer_id), this.execution_timer_id = null); - this.sendEventToAllNodes("onStop"); - } - }; - c.prototype.runStep = function(a, b, d) { - a = a || 1; - var q = e.getTime(); - this.globaltime = 0.001 * (q - this.starttime); - var n = this._nodes_executable ? this._nodes_executable : this._nodes; - if (n) { - d = d || n.length; - if (b) { - for (var f = 0; f < a; f++) { - for (var c = 0; c < d; ++c) { - var g = n[c]; - if (g.mode == e.ALWAYS && g.onExecute) { - g.onExecute(); - } - } - this.fixedtime += this.fixedtime_lapse; - if (this.onExecuteStep) { - this.onExecuteStep(); - } - } - if (this.onAfterExecute) { - this.onAfterExecute(); - } - } else { - try { - for (f = 0; f < a; f++) { - for (c = 0; c < d; ++c) { - if (g = n[c], g.mode == e.ALWAYS && g.onExecute) { - g.onExecute(); - } - } - this.fixedtime += this.fixedtime_lapse; - if (this.onExecuteStep) { - this.onExecuteStep(); - } - } - if (this.onAfterExecute) { - this.onAfterExecute(); - } - this.errors_in_execution = !1; - } catch (J) { - this.errors_in_execution = !0; - if (e.throw_errors) { - throw J; - } - e.debug && console.log("Error during execution: " + J); - this.stop(); - } - } - a = e.getTime(); - q = a - q; - 0 == q && (q = 1); - this.execution_time = 0.001 * q; - this.globaltime += 0.001 * q; - this.iteration += 1; - this.elapsed_time = 0.001 * (a - this.last_update_time); - this.last_update_time = a; - } - }; - c.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]); - } - }; - c.prototype.computeExecutionOrder = function(a, b) { - for (var d = [], q = [], n = {}, f = {}, c = {}, g = 0, l = this._nodes.length; g < l; ++g) { - var p = this._nodes[g]; - if (!a || p.onExecute) { - n[p.id] = p; - var m = 0; - if (p.inputs) { - for (var h = 0, r = p.inputs.length; h < r; h++) { - p.inputs[h] && null != p.inputs[h].link && (m += 1); - } - } - 0 == m ? (q.push(p), b && (p._level = 1)) : (b && (p._level = 0), c[p.id] = m); - } - } - for (; 0 != q.length;) { - if (p = q.shift(), d.push(p), delete n[p.id], p.outputs) { - for (g = 0; g < p.outputs.length; g++) { - if (a = p.outputs[g], null != a && null != a.links && 0 != a.links.length) { - for (h = 0; h < a.links.length; h++) { - (l = this.links[a.links[h]]) && !f[l.id] && (m = this.getNodeById(l.target_id), null == m ? f[l.id] = !0 : (b && (!m._level || m._level <= p._level) && (m._level = p._level + 1), f[l.id] = !0, --c[m.id], 0 == c[m.id] && q.push(m))); - } - } - } - } - } - for (g in n) { - d.push(n[g]); - } - d.length != this._nodes.length && e.debug && console.warn("something went wrong, nodes missing"); - l = d.length; - for (g = 0; g < l; ++g) { - d[g].order = g; - } - d = d.sort(function(a, b) { - var d = a.constructor.priority || a.priority || 0, e = b.constructor.priority || b.priority || 0; - return d == e ? a.order - b.order : d - e; - }); - for (g = 0; g < l; ++g) { - d[g].order = g; - } - return d; - }; - c.prototype.getAncestors = function(a) { - for (var b = [], d = [a], e = {}; d.length;) { - var n = d.shift(); - if (n.inputs) { - e[n.id] || n == a || (e[n.id] = !0, b.push(n)); - for (var f = 0; f < n.inputs.length; ++f) { - var c = n.getInputNode(f); - c && -1 == b.indexOf(c) && d.push(c); - } - } - } - b.sort(function(a, b) { - return a.order - b.order; - }); - return b; - }; - c.prototype.arrange = function(a) { - a = a || 100; - for (var b = this.computeExecutionOrder(!1, !0), d = [], q = 0; q < b.length; ++q) { - var n = b[q], f = n._level || 1; - d[f] || (d[f] = []); - d[f].push(n); - } - b = a; - for (q = 0; q < d.length; ++q) { - if (f = d[q]) { - for (var c = 100, g = a + e.NODE_TITLE_HEIGHT, l = 0; l < f.length; ++l) { - n = f[l], n.pos[0] = b, n.pos[1] = g, n.size[0] > c && (c = n.size[0]), g += n.size[1] + a + e.NODE_TITLE_HEIGHT; - } - b += c + a; - } - } - this.setDirtyCanvas(!0, !0); - }; - c.prototype.getTime = function() { - return this.globaltime; - }; - c.prototype.getFixedTime = function() { - return this.fixedtime; - }; - c.prototype.getElapsedTime = function() { - return this.elapsed_time; - }; - c.prototype.sendEventToAllNodes = function(a, b, d) { - d = d || e.ALWAYS; - var q = this._nodes_in_order ? this._nodes_in_order : this._nodes; - if (q) { - for (var n = 0, f = q.length; n < f; ++n) { - var c = q[n]; - if (c.constructor === e.Subgraph && "onExecute" != a) { - c.mode == d && c.sendEventToAllNodes(a, b, d); - } else { - if (c[a] && c.mode == d) { - if (void 0 === b) { - c[a](); - } else { - if (b && b.constructor === Array) { - c[a].apply(c, b); - } else { - c[a](b); - } - } - } - } - } - } - }; - c.prototype.sendActionToCanvas = function(a, b) { - if (this.list_of_graphcanvas) { - for (var d = 0; d < this.list_of_graphcanvas.length; ++d) { - var e = this.list_of_graphcanvas[d]; - e[a] && e[a].apply(e, b); - } - } - }; - c.prototype.add = function(a, b) { - if (a) { - if (a.constructor === m) { - 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 >= e.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; - } - } - }; - c.prototype.remove = function(a) { - if (a.constructor === e.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 d = a.inputs[b]; - null != d.link && a.disconnectInput(b); - } - } - if (a.outputs) { - for (b = 0; b < a.outputs.length; b++) { - d = a.outputs[b], null != d.links && d.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) { - d = this.list_of_graphcanvas[b], d.selected_nodes[a.id] && delete d.selected_nodes[a.id], d.node_dragged == a && (d.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(); - } - } - }; - c.prototype.getNodeById = function(a) { - return null == a ? null : this._nodes_by_id[a]; - }; - c.prototype.findNodesByClass = function(a, b) { - b = b || []; - for (var d = b.length = 0, e = this._nodes.length; d < e; ++d) { - this._nodes[d].constructor === a && b.push(this._nodes[d]); - } - return b; - }; - c.prototype.findNodesByType = function(a, b) { - a = a.toLowerCase(); - b = b || []; - for (var d = b.length = 0, e = this._nodes.length; d < e; ++d) { - this._nodes[d].type.toLowerCase() == a && b.push(this._nodes[d]); - } - return b; - }; - c.prototype.findNodeByTitle = function(a) { - for (var b = 0, d = this._nodes.length; b < d; ++b) { - if (this._nodes[b].title == a) { - return this._nodes[b]; - } - } - return null; - }; - c.prototype.findNodesByTitle = function(a) { - for (var b = [], d = 0, e = this._nodes.length; d < e; ++d) { - this._nodes[d].title == a && b.push(this._nodes[d]); - } - return b; - }; - c.prototype.getNodeOnPos = function(a, b, d, e) { - d = d || this._nodes; - for (var q = d.length - 1; 0 <= q; q--) { - var f = d[q]; - if (f.isPointInside(a, b, e)) { - return f; - } - } - return null; - }; - c.prototype.getGroupOnPos = function(a, b) { - for (var d = this._groups.length - 1; 0 <= d; d--) { - var e = this._groups[d]; - if (e.isPointInside(a, b, 2, !0)) { - return e; - } - } - return null; - }; - c.prototype.checkNodeTypes = function() { - for (var a = 0; a < this._nodes.length; a++) { - var b = this._nodes[a]; - if (b.constructor != e.registered_node_types[b.type]) { - console.log("node being replaced by newer version: " + b.type); - var d = e.createNode(b.type); - this._nodes[a] = d; - d.configure(b.serialize()); - d.graph = this; - this._nodes_by_id[d.id] = d; - b.inputs && (d.inputs = b.inputs.concat()); - b.outputs && (d.outputs = b.outputs.concat()); - } - } - this.updateExecutionOrder(); - }; - c.prototype.onAction = function(a, b) { - this._input_nodes = this.findNodesByClass(e.GraphInput, this._input_nodes); - for (var d = 0; d < this._input_nodes.length; ++d) { - var q = this._input_nodes[d]; - if (q.properties.name == a) { - q.onAction(a, b); - break; - } - } - }; - c.prototype.trigger = function(a, b) { - if (this.onTrigger) { - this.onTrigger(a, b); - } - }; - c.prototype.addInput = function(a, b, d) { - if (!this.inputs[a]) { - this.inputs[a] = {name:a, type:b, value:d}; - this._version++; - if (this.onInputAdded) { - this.onInputAdded(a, b); - } - if (this.onInputsOutputsChange) { - this.onInputsOutputsChange(); - } - } - }; - c.prototype.setInputData = function(a, b) { - if (a = this.inputs[a]) { - a.value = b; - } - }; - c.prototype.getInputData = function(a) { - return (a = this.inputs[a]) ? a.value : null; - }; - c.prototype.renameInput = function(a, b) { - if (b != a) { - if (!this.inputs[a]) { - return !1; - } - if (this.inputs[b]) { - return console.error("there is already one input with that name"), !1; - } - this.inputs[b] = this.inputs[a]; - delete this.inputs[a]; - this._version++; - if (this.onInputRenamed) { - this.onInputRenamed(a, b); - } - if (this.onInputsOutputsChange) { - this.onInputsOutputsChange(); - } - } - }; - c.prototype.changeInputType = function(a, b) { - if (!this.inputs[a]) { - return !1; - } - if (!this.inputs[a].type || String(this.inputs[a].type).toLowerCase() != String(b).toLowerCase()) { - if (this.inputs[a].type = b, this._version++, this.onInputTypeChanged) { - this.onInputTypeChanged(a, b); - } - } - }; - c.prototype.removeInput = function(a) { - if (!this.inputs[a]) { - return !1; - } - delete this.inputs[a]; - this._version++; - if (this.onInputRemoved) { - this.onInputRemoved(a); - } - if (this.onInputsOutputsChange) { - this.onInputsOutputsChange(); - } - return !0; - }; - c.prototype.addOutput = function(a, b, d) { - this.outputs[a] = {name:a, type:b, value:d}; - this._version++; - if (this.onOutputAdded) { - this.onOutputAdded(a, b); - } - if (this.onInputsOutputsChange) { - this.onInputsOutputsChange(); - } - }; - c.prototype.setOutputData = function(a, b) { - if (a = this.outputs[a]) { - a.value = b; - } - }; - c.prototype.getOutputData = function(a) { - return (a = this.outputs[a]) ? a.value : null; - }; - c.prototype.renameOutput = function(a, b) { - if (!this.outputs[a]) { - return !1; - } - if (this.outputs[b]) { - return console.error("there is already one output with that name"), !1; - } - this.outputs[b] = this.outputs[a]; - delete this.outputs[a]; - this._version++; - if (this.onOutputRenamed) { - this.onOutputRenamed(a, b); - } - if (this.onInputsOutputsChange) { - this.onInputsOutputsChange(); - } - }; - c.prototype.changeOutputType = function(a, b) { - if (!this.outputs[a]) { - return !1; - } - if (!this.outputs[a].type || String(this.outputs[a].type).toLowerCase() != String(b).toLowerCase()) { - if (this.outputs[a].type = b, this._version++, this.onOutputTypeChanged) { - this.onOutputTypeChanged(a, b); - } - } - }; - c.prototype.removeOutput = function(a) { - if (!this.outputs[a]) { - return !1; - } - delete this.outputs[a]; - this._version++; - if (this.onOutputRemoved) { - this.onOutputRemoved(a); - } - if (this.onInputsOutputsChange) { - this.onInputsOutputsChange(); - } - return !0; - }; - c.prototype.triggerInput = function(a, b) { - a = this.findNodesByTitle(a); - for (var d = 0; d < a.length; ++d) { - a[d].onTrigger(b); - } - }; - c.prototype.setCallback = function(a, b) { - a = this.findNodesByTitle(a); - for (var d = 0; d < a.length; ++d) { - a[d].setTrigger(b); - } - }; - c.prototype.connectionChange = function(a, b) { - this.updateExecutionOrder(); - if (this.onConnectionChange) { - this.onConnectionChange(a); - } - this._version++; - this.sendActionToCanvas("onConnectionChange"); - }; - c.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; - }; - c.prototype.clearTriggeredSlots = function() { - for (var a in this.links) { - var b = this.links[a]; - b && b._last_time && (b._last_time = 0); - } - }; - c.prototype.change = function() { - e.debug && console.log("Graph changed"); - this.sendActionToCanvas("setDirty", [!0, !0]); - if (this.on_change) { - this.on_change(this); - } - }; - c.prototype.setDirtyCanvas = function(a, b) { - this.sendActionToCanvas("setDirty", [a, b]); - }; - c.prototype.removeLink = function(a) { - if (a = this.links[a]) { - var b = this.getNodeById(a.target_id); - b && b.disconnectInput(a.target_slot); - } - }; - c.prototype.serialize = function() { - for (var a = [], b = 0, d = this._nodes.length; b < d; ++b) { - a.push(this._nodes[b].serialize()); - } - d = []; - for (b in this.links) { - var q = this.links[b]; - if (!q.serialize) { - console.warn("weird LLink bug, link info is not a LLink but a regular object"); - var n = new k; - for (f in q) { - n[f] = q[f]; - } - q = this.links[b] = n; - } - d.push(q.serialize()); - } - var f = []; - for (b = 0; b < this._groups.length; ++b) { - f.push(this._groups[b].serialize()); - } - return {last_node_id:this.last_node_id, last_link_id:this.last_link_id, nodes:a, links:d, groups:f, config:this.config, version:e.VERSION}; - }; - c.prototype.configure = function(a, b) { - if (a) { - b || this.clear(); - b = a.nodes; - if (a.links && a.links.constructor === Array) { - for (var d = [], q = 0; q < a.links.length; ++q) { - var n = a.links[q]; - if (n) { - var f = new k; - f.configure(n); - d[f.id] = f; - } else { - console.warn("serialized graph link data contains errors, skipping."); - } - } - a.links = d; - } - for (q in a) { - "nodes" != q && "groups" != q && (this[q] = a[q]); - } - d = !1; - this._nodes = []; - if (b) { - q = 0; - for (n = b.length; q < n; ++q) { - f = b[q]; - var c = e.createNode(f.type, f.title); - c || (e.debug && console.log("Node not found or has errors: " + f.type), c = new h, c.last_serialization = f, d = c.has_errors = !0); - c.id = f.id; - this.add(c, !0); - } - q = 0; - for (n = b.length; q < n; ++q) { - f = b[q], (c = this.getNodeById(f.id)) && c.configure(f); - } - } - this._groups.length = 0; - if (a.groups) { - for (q = 0; q < a.groups.length; ++q) { - b = new e.LGraphGroup, b.configure(a.groups[q]), this.add(b); - } - } - this.updateExecutionOrder(); - this._version++; - this.setDirtyCanvas(!0, !0); - return d; - } - }; - c.prototype.load = function(a) { - var b = this, d = new XMLHttpRequest; - d.open("GET", a, !0); - d.send(null); - d.onload = function(a) { - 200 !== d.status ? console.error("Error loading graph:", d.status, d.response) : (a = JSON.parse(d.response), b.configure(a)); - }; - d.onerror = function(a) { - console.error("Error loading graph:", a); - }; - }; - c.prototype.onNodeTrace = function(a, b, d) { - }; - k.prototype.configure = function(a) { - a.constructor === Array ? (this.id = a[0], this.origin_id = a[1], this.origin_slot = a[2], this.target_id = a[3], this.target_slot = a[4], this.type = a[5]) : (this.id = a.id, this.type = a.type, this.origin_id = a.origin_id, this.origin_slot = a.origin_slot, this.target_id = a.target_id, this.target_slot = a.target_slot); - }; - k.prototype.serialize = function() { - return [this.id, this.origin_id, this.origin_slot, this.target_id, this.target_slot, this.type]; - }; - e.LLink = k; - v.LGraphNode = e.LGraphNode = h; - h.prototype._ctor = function(a) { - this.title = a || "Unnamed"; - this.size = [e.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.flags = {}; - }; - h.prototype.configure = function(a) { - this.graph && this.graph._version++; - for (var b in a) { - if ("properties" == b) { - for (var d in a.properties) { - if (this.properties[d] = a.properties[d], this.onPropertyChanged) { - this.onPropertyChanged(d, a.properties[d]); - } - } - } else { - null != a[b] && ("object" == typeof a[b] ? this[b] && this[b].configure ? this[b].configure(a[b]) : this[b] = e.cloneObject(a[b], this[b]) : this[b] = a[b]); - } - } - a.title || (this.title = this.constructor.title); - if (this.onConnectionsChange) { - if (this.inputs) { - for (d = 0; d < this.inputs.length; ++d) { - b = this.inputs[d]; - var q = this.graph ? this.graph.links[b.link] : null; - this.onConnectionsChange(e.INPUT, d, !0, q, b); - } - } - if (this.outputs) { - for (d = 0; d < this.outputs.length; ++d) { - var n = this.outputs[d]; - if (n.links) { - for (b = 0; b < n.links.length; ++b) { - q = this.graph ? this.graph.links[n.links[b]] : null, this.onConnectionsChange(e.OUTPUT, d, !0, q, n); - } - } - } - } - } - if (this.widgets) { - for (d = 0; d < this.widgets.length; ++d) { - b = this.widgets[d], b.options && b.options.property && this.properties[b.options.property] && (b.value = JSON.parse(JSON.stringify(this.properties[b.options.property]))); - } - if (a.widgets_values) { - for (d = 0; d < a.widgets_values.length; ++d) { - this.widgets[d] && (this.widgets[d].value = a.widgets_values[d]); - } - } - } - if (this.onConfigure) { - this.onConfigure(a); - } - }; - h.prototype.serialize = function() { - var a = {id:this.id, type:this.type, pos:this.pos, size:this.size, flags:e.cloneObject(this.flags), order:this.order, mode:this.mode}; - if (this.constructor === h && this.last_serialization) { - return this.last_serialization; - } - 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 = e.cloneObject(this.properties)); - if (this.widgets && this.serialize_widgets) { - for (a.widgets_values = [], b = 0; b < this.widgets.length; ++b) { - a.widgets_values[b] = this.widgets[b].value; - } - } - 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); - this.onSerialize && this.onSerialize(a) && console.warn("node onSerialize shouldnt return anything, data should be stored in the object pass in the first parameter"); - return a; - }; - h.prototype.clone = function() { - var a = e.createNode(this.type); - if (!a) { - return null; - } - var b = e.cloneObject(this.serialize()); - if (b.inputs) { - for (var d = 0; d < b.inputs.length; ++d) { - b.inputs[d].link = null; - } - } - if (b.outputs) { - for (d = 0; d < b.outputs.length; ++d) { - b.outputs[d].links && (b.outputs[d].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.setProperty = function(a, b) { - this.properties || (this.properties = {}); - if (b !== this.properties[a]) { - var d = this.properties[a]; - this.properties[a] = b; - this.onPropertyChanged && !1 === this.onPropertyChanged(a, b, d) && (this.properties[a] = d); - } - }; - h.prototype.setOutputData = function(a, b) { - if (this.outputs && !(-1 == a || a >= this.outputs.length)) { - var d = this.outputs[a]; - if (d && (d._data = b, this.outputs[a].links)) { - for (d = 0; d < this.outputs[a].links.length; d++) { - var e = this.graph.links[this.outputs[a].links[d]]; - e && (e.data = b); - } - } - } - }; - h.prototype.setOutputDataType = function(a, b) { - if (this.outputs && !(-1 == a || a >= this.outputs.length)) { - var d = this.outputs[a]; - if (d && (d.type = b, this.outputs[a].links)) { - for (d = 0; d < this.outputs[a].links.length; d++) { - this.graph.links[this.outputs[a].links[d]].type = 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.getInputDataType = function(a) { - if (!this.inputs || a >= this.inputs.length || null == this.inputs[a].link) { - return null; - } - a = this.graph.links[this.inputs[a].link]; - if (!a) { - return null; - } - var b = this.graph.getNodeById(a.origin_id); - return b ? (a = b.outputs[a.origin_slot]) ? a.type : null : a.type; - }; - 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, d = this.inputs.length; b < d; ++b) { - var e = this.inputs[b]; - if (a == e.name && null != e.link && (e = this.graph.links[e.link])) { - return e.data; - } - } - 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 = [], d = 0; d < a.links.length; d++) { - var e = this.graph.links[a.links[d]]; - e && (e = this.graph.getNodeById(e.target_id)) && b.push(e); - } - return b; - }; - h.prototype.trigger = function(a, b) { - if (this.outputs && this.outputs.length) { - this.graph && (this.graph._last_trigger_time = e.getTime()); - for (var d = 0; d < this.outputs.length; ++d) { - var q = this.outputs[d]; - !q || q.type !== e.EVENT || a && q.name != a || this.triggerSlot(d, b); - } - } - }; - h.prototype.triggerSlot = function(a, b, d) { - if (this.outputs && (a = this.outputs[a]) && (a = a.links) && a.length) { - this.graph && (this.graph._last_trigger_time = e.getTime()); - for (var q = 0; q < a.length; ++q) { - var n = a[q]; - if (null == d || d == n) { - var f = this.graph.links[a[q]]; - if (f && (f._last_time = e.getTime(), n = this.graph.getNodeById(f.target_id))) { - if (f = n.inputs[f.target_slot], n.onAction) { - n.onAction(f.name, b); - } else { - if (n.mode === e.ON_TRIGGER && n.onExecute) { - n.onExecute(b); - } - } - } - } - } - } - }; - h.prototype.clearTriggeredSlot = function(a, b) { - if (this.outputs && (a = this.outputs[a]) && (a = a.links) && a.length) { - for (var d = 0; d < a.length; ++d) { - var e = a[d]; - if (null == b || b == e) { - if (e = this.graph.links[a[d]]) { - e._last_time = 0; - } - } - } - } - }; - h.prototype.addProperty = function(a, b, d, e) { - d = {name:a, type:d, default_value:b}; - if (e) { - for (var n in e) { - d[n] = e[n]; - } - } - this.properties_info || (this.properties_info = []); - this.properties_info.push(d); - this.properties || (this.properties = {}); - this.properties[a] = b; - return d; - }; - h.prototype.addOutput = function(a, b, d) { - a = {name:a, type:b, links:null}; - if (d) { - for (var e in d) { - a[e] = d[e]; - } - } - this.outputs || (this.outputs = []); - this.outputs.push(a); - if (this.onOutputAdded) { - this.onOutputAdded(a); - } - this.size = this.computeSize(); - this.setDirtyCanvas(!0, !0); - return a; - }; - h.prototype.addOutputs = function(a) { - for (var b = 0; b < a.length; ++b) { - var d = a[b], e = {name:d[0], type:d[1], link:null}; - if (a[2]) { - for (var n in d[2]) { - e[n] = d[2][n]; - } - } - this.outputs || (this.outputs = []); - this.outputs.push(e); - if (this.onOutputAdded) { - this.onOutputAdded(e); - } - } - this.size = this.computeSize(); - this.setDirtyCanvas(!0, !0); - }; - h.prototype.removeOutput = function(a) { - this.disconnectOutput(a); - this.outputs.splice(a, 1); - for (var b = a; b < this.outputs.length; ++b) { - if (this.outputs[b] && this.outputs[b].links) { - for (var d = this.outputs[b].links, e = 0; e < d.length; ++e) { - var n = this.graph.links[d[e]]; - n && --n.origin_slot; - } - } - } - this.size = this.computeSize(); - if (this.onOutputRemoved) { - this.onOutputRemoved(a); - } - this.setDirtyCanvas(!0, !0); - }; - h.prototype.addInput = function(a, b, d) { - a = {name:a, type:b || 0, link:null}; - if (d) { - for (var e in d) { - a[e] = d[e]; - } - } - this.inputs || (this.inputs = []); - this.inputs.push(a); - this.size = this.computeSize(); - if (this.onInputAdded) { - this.onInputAdded(a); - } - this.setDirtyCanvas(!0, !0); - return a; - }; - h.prototype.addInputs = function(a) { - for (var b = 0; b < a.length; ++b) { - var d = a[b], e = {name:d[0], type:d[1], link:null}; - if (a[2]) { - for (var n in d[2]) { - e[n] = d[2][n]; - } - } - this.inputs || (this.inputs = []); - this.inputs.push(e); - if (this.onInputAdded) { - this.onInputAdded(e); - } - } - this.size = this.computeSize(); - this.setDirtyCanvas(!0, !0); - }; - h.prototype.removeInput = function(a) { - this.disconnectInput(a); - this.inputs.splice(a, 1); - for (var b = a; b < this.inputs.length; ++b) { - if (this.inputs[b]) { - var d = this.graph.links[this.inputs[b].link]; - d && --d.target_slot; - } - } - this.size = this.computeSize(); - if (this.onInputRemoved) { - this.onInputRemoved(a); - } - this.setDirtyCanvas(!0, !0); - }; - h.prototype.addConnection = function(a, b, d, e) { - a = {name:a, type:b, pos:d, direction:e, links:null}; - this.connections.push(a); - return a; - }; - h.prototype.computeSize = function(a, b) { - function d(a) { - return a ? f * a.length * 0.6 : 0; - } - if (this.constructor.size) { - return this.constructor.size.concat(); - } - 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 f = e.NODE_TEXT_SIZE; - b[1] = (this.constructor.slot_start_y || 0) + a * e.NODE_SLOT_HEIGHT; - a = 0; - this.widgets && this.widgets.length && (a = this.widgets.length * (e.NODE_WIDGET_HEIGHT + 4) + 8); - b[1] = this.widgets_up ? Math.max(b[1], a) : null != this.widgets_start_y ? Math.max(b[1], a + this.widgets_start_y) : b[1] + a; - a = d(this.title); - var n = 0, c = 0; - if (this.inputs) { - for (var g = 0, l = this.inputs.length; g < l; ++g) { - var p = this.inputs[g]; - p = p.label || p.name || ""; - p = d(p); - n < p && (n = p); - } - } - if (this.outputs) { - for (g = 0, l = this.outputs.length; g < l; ++g) { - p = this.outputs[g], p = p.label || p.name || "", p = d(p), c < p && (c = p); - } - } - b[0] = Math.max(n + c + 10, a); - b[0] = Math.max(b[0], e.NODE_WIDTH); - this.widgets && this.widgets.length && (b[0] = Math.max(b[0], 1.5 * e.NODE_WIDTH)); - if (this.onResize) { - this.onResize(b); - } - this.constructor.min_height && b[1] < this.constructor.min_height && (b[1] = this.constructor.min_height); - b[1] += 6; - return b; - }; - h.prototype.addWidget = function(a, b, d, e, n) { - this.widgets || (this.widgets = []); - !n && e && e.constructor === Object && (n = e, e = null); - n && n.constructor === String && (n = {property:n}); - e && e.constructor === String && (n || (n = {}), n.property = e, e = null); - e && e.constructor !== Function && (console.warn("addWidget: callback must be a function"), e = null); - b = {type:a.toLowerCase(), name:b, value:d, callback:e, options:n || {}}; - void 0 !== b.options.y && (b.y = b.options.y); - e || b.options.callback || b.options.property || console.warn("LiteGraph addWidget(...) without a callback or property assigned"); - if ("combo" == a && !b.options.values) { - throw "LiteGraph addWidget('combo',...) requires to pass values in options: { values:['red','blue'] }"; - } - this.widgets.push(b); - this.size = this.computeSize(); - return b; - }; - h.prototype.addCustomWidget = function(a) { - this.widgets || (this.widgets = []); - this.widgets.push(a); - return a; - }; - h.prototype.getBounding = function(a) { - a = a || new Float32Array(4); - a[0] = this.pos[0] - 4; - a[1] = this.pos[1] - e.NODE_TITLE_HEIGHT; - a[2] = this.size[0] + 4; - a[3] = this.size[1] + e.NODE_TITLE_HEIGHT; - if (this.onBounding) { - this.onBounding(a); - } - return a; - }; - h.prototype.isPointInside = function(a, b, d, f) { - d = d || 0; - var n = this.graph && this.graph.isLive() ? 0 : e.NODE_TITLE_HEIGHT; - f && (n = 0); - if (this.flags && this.flags.collapsed) { - if (B(a, b, this.pos[0] - d, this.pos[1] - e.NODE_TITLE_HEIGHT - d, (this._collapsed_width || e.NODE_COLLAPSED_WIDTH) + 2 * d, e.NODE_TITLE_HEIGHT + 2 * d)) { - return !0; - } - } else { - if (this.pos[0] - 4 - d < a && this.pos[0] + this.size[0] + 4 + d > a && this.pos[1] - n - d < b && this.pos[1] + this.size[1] + d > b) { - return !0; - } - } - return !1; - }; - h.prototype.getSlotInPosition = function(a, b) { - var d = new Float32Array(2); - if (this.inputs) { - for (var e = 0, n = this.inputs.length; e < n; ++e) { - var f = this.inputs[e]; - this.getConnectionPos(!0, e, d); - if (B(a, b, d[0] - 10, d[1] - 5, 20, 10)) { - return {input:f, slot:e, link_pos:d}; - } - } - } - if (this.outputs) { - for (e = 0, n = this.outputs.length; e < n; ++e) { - if (f = this.outputs[e], this.getConnectionPos(!1, e, d), B(a, b, d[0] - 10, d[1] - 5, 20, 10)) { - return {output:f, slot:e, link_pos:d}; - } - } - } - return null; - }; - h.prototype.findInputSlot = function(a) { - if (!this.inputs) { - return -1; - } - for (var b = 0, d = this.inputs.length; b < d; ++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, d = this.outputs.length; b < d; ++b) { - if (a == this.outputs[b].name) { - return b; - } - } - return -1; - }; - h.prototype.connect = function(a, b, d) { - d = d || 0; - if (!this.graph) { - return console.log("Connect: Error, node doesn't belong to any graph. Nodes must be added first to a graph before connecting them."), null; - } - if (a.constructor === String) { - if (a = this.findOutputSlot(a), -1 == a) { - return e.debug && console.log("Connect: Error, no slot of name " + a), null; - } - } else { - if (!this.outputs || a >= this.outputs.length) { - return e.debug && console.log("Connect: Error, slot number not found"), null; - } - } - b && b.constructor === Number && (b = this.graph.getNodeById(b)); - if (!b) { - throw "target node is null"; - } - if (b == this) { - return null; - } - if (d.constructor === String) { - if (d = b.findInputSlot(d), -1 == d) { - return e.debug && console.log("Connect: Error, no slot of name " + d), null; - } - } else { - if (d === e.EVENT) { - return null; - } - if (!b.inputs || d >= b.inputs.length) { - return e.debug && console.log("Connect: Error, slot number not found"), null; - } - } - null != b.inputs[d].link && b.disconnectInput(d); - var f = this.outputs[a]; - if (b.onConnectInput && !1 === b.onConnectInput(d, f.type, f)) { - return null; - } - var n = b.inputs[d], c = null; - if (e.isValidConnection(f.type, n.type)) { - c = new k(++this.graph.last_link_id, n.type, this.id, a, b.id, d); - this.graph.links[c.id] = c; - null == f.links && (f.links = []); - f.links.push(c.id); - b.inputs[d].link = c.id; - this.graph && this.graph._version++; - if (this.onConnectionsChange) { - this.onConnectionsChange(e.OUTPUT, a, !0, c, f); - } - if (b.onConnectionsChange) { - b.onConnectionsChange(e.INPUT, d, !0, c, n); - } - this.graph && this.graph.onNodeConnectionChange && (this.graph.onNodeConnectionChange(e.INPUT, b, d, this, a), this.graph.onNodeConnectionChange(e.OUTPUT, this, a, b, d)); - } - this.setDirtyCanvas(!1, !0); - this.graph.connectionChange(this, c); - return c; - }; - h.prototype.disconnectOutput = function(a, b) { - if (a.constructor === String) { - if (a = this.findOutputSlot(a), -1 == a) { - return e.debug && console.log("Connect: Error, no slot of name " + a), !1; - } - } else { - if (!this.outputs || a >= this.outputs.length) { - return e.debug && console.log("Connect: Error, slot number not found"), !1; - } - } - var d = this.outputs[a]; - if (!d || !d.links || 0 == d.links.length) { - return !1; - } - if (b) { - b.constructor === Number && (b = this.graph.getNodeById(b)); - if (!b) { - throw "Target Node not found"; - } - for (var f = 0, n = d.links.length; f < n; f++) { - var c = d.links[f], g = this.graph.links[c]; - if (g.target_id == b.id) { - d.links.splice(f, 1); - var l = b.inputs[g.target_slot]; - l.link = null; - delete this.graph.links[c]; - this.graph && this.graph._version++; - if (b.onConnectionsChange) { - b.onConnectionsChange(e.INPUT, g.target_slot, !1, g, l); - } - if (this.onConnectionsChange) { - this.onConnectionsChange(e.OUTPUT, a, !1, g, d); - } - if (this.graph && this.graph.onNodeConnectionChange) { - this.graph.onNodeConnectionChange(e.OUTPUT, this, a); - } - this.graph && this.graph.onNodeConnectionChange && (this.graph.onNodeConnectionChange(e.OUTPUT, this, a), this.graph.onNodeConnectionChange(e.INPUT, b, g.target_slot)); - break; - } - } - } else { - f = 0; - for (n = d.links.length; f < n; f++) { - if (c = d.links[f], g = this.graph.links[c]) { - b = this.graph.getNodeById(g.target_id); - this.graph && this.graph._version++; - if (b) { - l = b.inputs[g.target_slot]; - l.link = null; - if (b.onConnectionsChange) { - b.onConnectionsChange(e.INPUT, g.target_slot, !1, g, l); - } - if (this.graph && this.graph.onNodeConnectionChange) { - this.graph.onNodeConnectionChange(e.INPUT, b, g.target_slot); - } - } - delete this.graph.links[c]; - if (this.onConnectionsChange) { - this.onConnectionsChange(e.OUTPUT, a, !1, g, d); - } - this.graph && this.graph.onNodeConnectionChange && (this.graph.onNodeConnectionChange(e.OUTPUT, this, a), this.graph.onNodeConnectionChange(e.INPUT, b, g.target_slot)); - } - } - d.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 e.debug && console.log("Connect: Error, no slot of name " + a), !1; - } - } else { - if (!this.inputs || a >= this.inputs.length) { - return e.debug && console.log("Connect: Error, slot number not found"), !1; - } - } - var b = this.inputs[a]; - if (!b) { - return !1; - } - var d = this.inputs[a].link; - this.inputs[a].link = null; - var f = this.graph.links[d]; - if (f) { - var n = this.graph.getNodeById(f.origin_id); - if (!n) { - return !1; - } - var c = n.outputs[f.origin_slot]; - if (!c || !c.links || 0 == c.links.length) { - return !1; - } - for (var g = 0, l = c.links.length; g < l; g++) { - if (c.links[g] == d) { - c.links.splice(g, 1); - break; - } - } - delete this.graph.links[d]; - this.graph && this.graph._version++; - if (this.onConnectionsChange) { - this.onConnectionsChange(e.INPUT, a, !1, f, b); - } - if (n.onConnectionsChange) { - n.onConnectionsChange(e.OUTPUT, g, !1, f, c); - } - this.graph && this.graph.onNodeConnectionChange && (this.graph.onNodeConnectionChange(e.OUTPUT, n, g), this.graph.onNodeConnectionChange(e.INPUT, this, a)); - } - this.setDirtyCanvas(!1, !0); - this.graph.connectionChange(this); - return !0; - }; - h.prototype.getConnectionPos = function(a, b, d) { - d = d || new Float32Array(2); - var f = 0; - a && this.inputs && (f = this.inputs.length); - !a && this.outputs && (f = this.outputs.length); - var n = 0.5 * e.NODE_SLOT_HEIGHT; - if (this.flags.collapsed) { - return b = this._collapsed_width || e.NODE_COLLAPSED_WIDTH, this.horizontal ? (d[0] = this.pos[0] + 0.5 * b, d[1] = a ? this.pos[1] - e.NODE_TITLE_HEIGHT : this.pos[1]) : (d[0] = a ? this.pos[0] : this.pos[0] + b, d[1] = this.pos[1] - 0.5 * e.NODE_TITLE_HEIGHT), d; - } - if (a && -1 == b) { - return d[0] = this.pos[0] + 0.5 * e.NODE_TITLE_HEIGHT, d[1] = this.pos[1] + 0.5 * e.NODE_TITLE_HEIGHT, d; - } - if (a && f > b && this.inputs[b].pos) { - return d[0] = this.pos[0] + this.inputs[b].pos[0], d[1] = this.pos[1] + this.inputs[b].pos[1], d; - } - if (!a && f > b && this.outputs[b].pos) { - return d[0] = this.pos[0] + this.outputs[b].pos[0], d[1] = this.pos[1] + this.outputs[b].pos[1], d; - } - if (this.horizontal) { - return d[0] = this.pos[0] + this.size[0] / f * (b + 0.5), d[1] = a ? this.pos[1] - e.NODE_TITLE_HEIGHT : this.pos[1] + this.size[1], d; - } - d[0] = a ? this.pos[0] + n : this.pos[0] + this.size[0] + 1 - n; - d[1] = this.pos[1] + (b + 0.7) * e.NODE_SLOT_HEIGHT + (this.constructor.slot_start_y || 0); - return d; - }; - h.prototype.alignToGrid = function() { - this.pos[0] = e.CANVAS_GRID_SIZE * Math.round(this.pos[0] / e.CANVAS_GRID_SIZE); - this.pos[1] = e.CANVAS_GRID_SIZE * Math.round(this.pos[1] / e.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 = e.node_images_path + a; - b.ready = !1; - var d = this; - b.onload = function() { - this.ready = !0; - d.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, d = 0; d < b.length; ++d) { - var e = b[d]; - if (a || e.node_capturing_input == this) { - e.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, d) { - return [(a + this.pos[0]) * d.scale + d.offset[0], (b + this.pos[1]) * d.scale + d.offset[1]]; - }; - v.LGraphGroup = e.LGraphGroup = m; - m.prototype._ctor = function(a) { - this.title = a || "Group"; - this.font_size = 24; - this.color = g.node_colors.pale_blue ? g.node_colors.pale_blue.groupcolor : "#AAA"; - 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.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}); - }; - m.prototype.configure = function(a) { - this.title = a.title; - this._bounding.set(a.bounding); - this.color = a.color; - this.font = a.font; - }; - m.prototype.serialize = function() { - var a = this._bounding; - return {title:this.title, bounding:[Math.round(a[0]), Math.round(a[1]), Math.round(a[2]), Math.round(a[3])], color:this.color, font:this.font}; - }; - m.prototype.move = function(a, b, d) { - this._pos[0] += a; - this._pos[1] += b; - if (!d) { - for (d = 0; d < this._nodes.length; ++d) { - var e = this._nodes[d]; - e.pos[0] += a; - e.pos[1] += b; - } - } - }; - m.prototype.recomputeInsideNodes = function() { - this._nodes.length = 0; - for (var a = this.graph._nodes, b = new Float32Array(4), d = 0; d < a.length; ++d) { - var e = a[d]; - e.getBounding(b); - x(this._bounding, b) && this._nodes.push(e); - } - }; - m.prototype.isPointInside = h.prototype.isPointInside; - m.prototype.setDirtyCanvas = h.prototype.setDirtyCanvas; - e.DragAndScale = t; - t.prototype.bindEvents = function(a) { - this.last_mouse = new Float32Array(2); - this._binded_mouse_callback = this.onMouse.bind(this); - a.addEventListener("mousedown", this._binded_mouse_callback); - a.addEventListener("mousemove", this._binded_mouse_callback); - a.addEventListener("mousewheel", this._binded_mouse_callback, !1); - a.addEventListener("wheel", this._binded_mouse_callback, !1); - }; - t.prototype.computeVisibleArea = function() { - if (this.element) { - var a = -this.offset[0], b = -this.offset[1], d = a + this.element.width / this.scale, e = b + this.element.height / this.scale; - this.visible_area[0] = a; - this.visible_area[1] = b; - this.visible_area[2] = d - a; - this.visible_area[3] = e - b; - } else { - this.visible_area[0] = this.visible_area[1] = this.visible_area[2] = this.visible_area[3] = 0; - } - }; - t.prototype.onMouse = function(a) { - if (this.enabled) { - var b = this.element, d = b.getBoundingClientRect(), e = a.clientX - d.left; - d = a.clientY - d.top; - a.canvasx = e; - a.canvasy = d; - a.dragging = this.dragging; - var f = !1; - this.onmouse && (f = this.onmouse(a)); - if ("mousedown" == a.type) { - this.dragging = !0, b.removeEventListener("mousemove", this._binded_mouse_callback), document.body.addEventListener("mousemove", this._binded_mouse_callback), document.body.addEventListener("mouseup", this._binded_mouse_callback); - } else { - if ("mousemove" == a.type) { - f || (b = e - this.last_mouse[0], f = d - this.last_mouse[1], this.dragging && this.mouseDrag(b, f)); - } else { - if ("mouseup" == a.type) { - this.dragging = !1, document.body.removeEventListener("mousemove", this._binded_mouse_callback), document.body.removeEventListener("mouseup", this._binded_mouse_callback), b.addEventListener("mousemove", this._binded_mouse_callback); - } else { - if ("mousewheel" == a.type || "wheel" == a.type || "DOMMouseScroll" == a.type) { - a.eventType = "mousewheel", a.wheel = "wheel" == a.type ? -a.deltaY : null != a.wheelDeltaY ? a.wheelDeltaY : -60 * a.detail, a.delta = a.wheelDelta ? a.wheelDelta / 40 : a.deltaY ? -a.deltaY / 3 : 0, this.changeDeltaScale(1.0 + 0.05 * a.delta); - } - } - } - } - this.last_mouse[0] = e; - this.last_mouse[1] = d; - a.preventDefault(); - a.stopPropagation(); - return !1; - } - }; - t.prototype.toCanvasContext = function(a) { - a.scale(this.scale, this.scale); - a.translate(this.offset[0], this.offset[1]); - }; - t.prototype.convertOffsetToCanvas = function(a) { - return [(a[0] + this.offset[0]) * this.scale, (a[1] + this.offset[1]) * this.scale]; - }; - t.prototype.convertCanvasToOffset = function(a, b) { - b = b || [0, 0]; - b[0] = a[0] / this.scale - this.offset[0]; - b[1] = a[1] / this.scale - this.offset[1]; - return b; - }; - t.prototype.mouseDrag = function(a, b) { - this.offset[0] += a / this.scale; - this.offset[1] += b / this.scale; - if (this.onredraw) { - this.onredraw(this); - } - }; - t.prototype.changeScale = function(a, b) { - a < this.min_scale ? a = this.min_scale : a > this.max_scale && (a = this.max_scale); - if (a != this.scale && this.element) { - var d = this.element.getBoundingClientRect(); - if (d && (b = b || [0.5 * d.width, 0.5 * d.height], d = this.convertCanvasToOffset(b), this.scale = a, 0.01 > Math.abs(this.scale - 1) && (this.scale = 1), a = this.convertCanvasToOffset(b), a = [a[0] - d[0], a[1] - d[1]], this.offset[0] += a[0], this.offset[1] += a[1], this.onredraw)) { - this.onredraw(this); - } - } - }; - t.prototype.changeDeltaScale = function(a, b) { - this.changeScale(this.scale * a, b); - }; - t.prototype.reset = function() { - this.scale = 1; - this.offset[0] = 0; - this.offset[1] = 0; - }; - v.LGraphCanvas = e.LGraphCanvas = g; - g.link_type_colors = {"-1":e.EVENT_LINK_COLOR, number:"#AAA", node:"#DCA"}; - g.gradients = {}; - g.prototype.clear = function() { - this.fps = this.render_time = this.last_draw_time = this.frame = 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; - this.visible_area.set([0, 0, 0, 0]); - if (this.onClear) { - this.onClear(); - } - }; - g.prototype.setGraph = function(a, b) { - this.graph != a && (b || this.clear(), !a && this.graph ? this.graph.detachCanvas(this) : (a.attachCanvas(this), this.setDirty(!0, !0))); - }; - g.prototype.openSubgraph = function(a) { - if (!a) { - throw "graph cannot be null"; - } - if (this.graph == a) { - throw "graph cannot be the same"; - } - this.clear(); - this.graph && (this._graph_stack || (this._graph_stack = []), this._graph_stack.push(this.graph)); - a.attachCanvas(this); - this.setDirty(!0, !0); - }; - g.prototype.closeSubgraph = function() { - if (this._graph_stack && 0 != this._graph_stack.length) { - var a = this.graph._subgraph_node, b = this._graph_stack.pop(); - this.selected_nodes = {}; - this.highlighted_links = {}; - b.attachCanvas(this); - this.setDirty(!0, !0); - a && (this.centerOnNode(a), this.selectNodes([a])); - } - }; - g.prototype.getCurrentGraph = function() { - return this.graph; - }; - g.prototype.setCanvas = function(a, b) { - if (a && a.constructor === String && (a = document.getElementById(a), !a)) { - throw "Error creating LiteGraph canvas: Canvas not found"; - } - if (a !== this.canvas && (!a && this.canvas && (b || this.unbindEvents()), this.canvas = a, this.ds.element = a)) { - a.className += " lgraphcanvas"; - a.data = this; - a.tabindex = "1"; - this.bgcanvas = null; - this.bgcanvas || (this.bgcanvas = document.createElement("canvas"), this.bgcanvas.width = this.canvas.width, this.bgcanvas.height = this.canvas.height); - if (null == a.getContext) { - if ("canvas" != a.localName) { - throw "Element supplied for LGraphCanvas must be a element, you passed a " + a.localName; - } - throw "This browser doesn't support Canvas"; - } - null == (this.ctx = a.getContext("2d")) && (a.webgl_enabled || console.warn("This canvas seems to be WebGL, enabling WebGL renderer"), this.enableWebGL()); - this._mousemove_callback = this.processMouseMove.bind(this); - this._mouseup_callback = this.processMouseUp.bind(this); - b || this.bindEvents(); - } - }; - g.prototype._doNothing = function(a) { - a.preventDefault(); - return !1; - }; - g.prototype._doReturnTrue = function(a) { - a.preventDefault(); - return !0; - }; - g.prototype.bindEvents = function() { - if (this._events_binded) { - console.warn("LGraphCanvas: events already binded"); - } else { - var a = this.canvas, b = this.getCanvasWindow().document; - this._mousedown_callback = this.processMouseDown.bind(this); - this._mousewheel_callback = this.processMouseWheel.bind(this); - a.addEventListener("mousedown", this._mousedown_callback, !0); - a.addEventListener("mousemove", this._mousemove_callback); - a.addEventListener("mousewheel", this._mousewheel_callback, !1); - a.addEventListener("contextmenu", this._doNothing); - a.addEventListener("DOMMouseScroll", this._mousewheel_callback, !1); - a.addEventListener("touchstart", this.touchHandler, !0); - a.addEventListener("touchmove", this.touchHandler, !0); - a.addEventListener("touchend", this.touchHandler, !0); - a.addEventListener("touchcancel", this.touchHandler, !0); - this._key_callback = this.processKey.bind(this); - a.addEventListener("keydown", this._key_callback, !0); - b.addEventListener("keyup", this._key_callback, !0); - this._ondrop_callback = this.processDrop.bind(this); - a.addEventListener("dragover", this._doNothing, !1); - a.addEventListener("dragend", this._doNothing, !1); - a.addEventListener("drop", this._ondrop_callback, !1); - a.addEventListener("dragenter", this._doReturnTrue, !1); - this._events_binded = !0; - } - }; - g.prototype.unbindEvents = function() { - if (this._events_binded) { - var a = this.getCanvasWindow().document; - this.canvas.removeEventListener("mousedown", this._mousedown_callback); - this.canvas.removeEventListener("mousewheel", this._mousewheel_callback); - this.canvas.removeEventListener("DOMMouseScroll", this._mousewheel_callback); - this.canvas.removeEventListener("keydown", this._key_callback); - a.removeEventListener("keyup", this._key_callback); - this.canvas.removeEventListener("contextmenu", this._doNothing); - this.canvas.removeEventListener("drop", this._ondrop_callback); - this.canvas.removeEventListener("dragenter", this._doReturnTrue); - this.canvas.removeEventListener("touchstart", this.touchHandler); - this.canvas.removeEventListener("touchmove", this.touchHandler); - this.canvas.removeEventListener("touchend", this.touchHandler); - this.canvas.removeEventListener("touchcancel", this.touchHandler); - this._ondrop_callback = this._key_callback = this._mousewheel_callback = this._mousedown_callback = null; - this._events_binded = !1; - } else { - console.warn("LGraphCanvas: no events binded"); - } - }; - g.getFileExtension = function(a) { - var b = a.indexOf("?"); - -1 != b && (a = a.substr(0, b)); - b = a.lastIndexOf("."); - return -1 == b ? "" : a.substr(b + 1).toLowerCase(); - }; - g.prototype.enableWebGL = function() { - this.gl = this.ctx = enableWebGLCanvas(this.canvas); - this.ctx.webgl = !0; - this.bgcanvas = this.canvas; - this.bgctx = this.gl; - this.canvas.webgl_enabled = !0; - }; - g.prototype.setDirty = function(a, b) { - a && (this.dirty_canvas = !0); - b && (this.dirty_bgcanvas = !0); - }; - g.prototype.getCanvasWindow = function() { - if (!this.canvas) { - return window; - } - var a = this.canvas.ownerDocument; - return a.defaultView || a.parentWindow; - }; - g.prototype.startRendering = function() { - function a() { - this.pause_rendering || this.draw(); - var b = this.getCanvasWindow(); - this.is_rendering && b.requestAnimationFrame(a.bind(this)); - } - this.is_rendering || (this.is_rendering = !0, a.call(this)); - }; - g.prototype.stopRendering = function() { - this.is_rendering = !1; - }; - g.prototype.processMouseDown = function(a) { - if (this.graph) { - this.adjustMouseEvent(a); - var b = this.getCanvasWindow(); - g.active_canvas = this; - this.canvas.removeEventListener("mousemove", this._mousemove_callback); - b.document.addEventListener("mousemove", this._mousemove_callback, !0); - b.document.addEventListener("mouseup", this._mouseup_callback, !0); - var d = this.graph.getNodeOnPos(a.canvasX, a.canvasY, this.visible_nodes, 5), f = !1, n = 300 > e.getTime() - this.last_mouseclick; - this.canvas_mouse[0] = a.canvasX; - this.canvas_mouse[1] = a.canvasY; - this.canvas.focus(); - e.closeAllContextMenus(b); - if (!this.onMouse || 1 != this.onMouse(a)) { - if (1 == a.which) { - a.ctrlKey && (this.dragging_rectangle = new Float32Array(4), this.dragging_rectangle[0] = a.canvasX, this.dragging_rectangle[1] = a.canvasY, this.dragging_rectangle[2] = 1, this.dragging_rectangle[3] = 1, f = !0); - var c = !1; - if (d && this.allow_interaction && !f && !this.read_only) { - this.live_mode || d.flags.pinned || this.bringToFront(d); - if (!this.connecting_node && !d.flags.collapsed && !this.live_mode) { - if (!f && !1 !== d.resizable && B(a.canvasX, a.canvasY, d.pos[0] + d.size[0] - 5, d.pos[1] + d.size[1] - 5, 10, 10)) { - this.resizing_node = d, this.canvas.style.cursor = "se-resize", f = !0; - } else { - if (d.outputs) { - for (var l = 0, p = d.outputs.length; l < p; ++l) { - var m = d.outputs[l], h = d.getConnectionPos(!1, l); - if (B(a.canvasX, a.canvasY, h[0] - 15, h[1] - 10, 30, 20)) { - this.connecting_node = d; - this.connecting_output = m; - this.connecting_pos = d.getConnectionPos(!1, l); - this.connecting_slot = l; - a.shiftKey && d.disconnectOutput(l); - if (n) { - if (d.onOutputDblClick) { - d.onOutputDblClick(l, a); - } - } else { - if (d.onOutputClick) { - d.onOutputClick(l, a); - } - } - f = !0; - break; - } - } - } - if (d.inputs) { - for (l = 0, p = d.inputs.length; l < p; ++l) { - if (m = d.inputs[l], h = d.getConnectionPos(!0, l), B(a.canvasX, a.canvasY, h[0] - 15, h[1] - 10, 30, 20)) { - if (n) { - if (d.onInputDblClick) { - d.onInputDblClick(l, a); - } - } else { - if (d.onInputClick) { - d.onInputClick(l, a); - } - } - if (null !== m.link) { - f = this.graph.links[m.link]; - d.disconnectInput(l); - if (this.allow_reconnect_links || a.shiftKey) { - this.connecting_node = this.graph._nodes_by_id[f.origin_id], this.connecting_slot = f.origin_slot, this.connecting_output = this.connecting_node.outputs[this.connecting_slot], this.connecting_pos = this.connecting_node.getConnectionPos(!1, this.connecting_slot); - } - f = this.dirty_bgcanvas = !0; - } - } - } - } - } - } - if (!f) { - l = !1; - if (p = this.processNodeWidgets(d, this.canvas_mouse, a)) { - l = !0, this.node_widget = [d, p]; - } - if (n && this.selected_nodes[d.id]) { - if (d.onDblClick) { - d.onDblClick(a, [a.canvasX - d.pos[0], a.canvasY - d.pos[1]], this); - } - this.processNodeDblClicked(d); - l = !0; - } - d.onMouseDown && d.onMouseDown(a, [a.canvasX - d.pos[0], a.canvasY - d.pos[1]], this) ? l = !0 : this.live_mode && (l = c = !0); - l || (this.allow_dragnodes && (this.node_dragged = d), this.selected_nodes[d.id] || this.processNodeSelected(d, a)); - this.dirty_canvas = !0; - } - } else { - if (!this.read_only) { - for (l = 0; l < this.visible_links.length; ++l) { - if (d = this.visible_links[l], c = d._pos, !(!c || a.canvasX < c[0] - 4 || a.canvasX > c[0] + 4 || a.canvasY < c[1] - 4 || a.canvasY > c[1] + 4)) { - this.showLinkMenu(d, a); - this.over_link_center = null; - break; - } - } - } - this.selected_group = this.graph.getGroupOnPos(a.canvasX, a.canvasY); - this.selected_group_resizing = !1; - this.selected_group && !this.read_only && (a.ctrlKey && (this.dragging_rectangle = null), 10 > D([a.canvasX, a.canvasY], [this.selected_group.pos[0] + this.selected_group.size[0], this.selected_group.pos[1] + this.selected_group.size[1]]) * this.ds.scale ? this.selected_group_resizing = !0 : this.selected_group.recomputeInsideNodes()); - n && !this.read_only && this.allow_searchbox && this.showSearchBox(a); - c = !0; - } - !f && c && this.allow_dragcanvas && (this.dragging_canvas = !0); - } else { - 2 != a.which && 3 == a.which && (this.read_only || this.processContextMenu(d, a)); - } - this.last_mouse[0] = a.localX; - this.last_mouse[1] = a.localY; - this.last_mouseclick = e.getTime(); - this.last_mouse_dragging = !0; - this.graph.change(); - (!b.document.activeElement || "input" != b.document.activeElement.nodeName.toLowerCase() && "textarea" != b.document.activeElement.nodeName.toLowerCase()) && a.preventDefault(); - a.stopPropagation(); - if (this.onMouseDown) { - this.onMouseDown(a); - } - return !1; - } - } - }; - g.prototype.processMouseMove = function(a) { - this.autoresize && this.resize(); - if (this.graph) { - g.active_canvas = this; - this.adjustMouseEvent(a); - var b = [a.localX, a.localY], d = [b[0] - this.last_mouse[0], b[1] - this.last_mouse[1]]; - this.last_mouse = b; - this.canvas_mouse[0] = a.canvasX; - this.canvas_mouse[1] = a.canvasY; - a.dragging = this.last_mouse_dragging; - this.node_widget && (this.processNodeWidgets(this.node_widget[0], this.canvas_mouse, a, this.node_widget[1]), this.dirty_canvas = !0); - if (this.dragging_rectangle) { - this.dragging_rectangle[2] = a.canvasX - this.dragging_rectangle[0], this.dragging_rectangle[3] = a.canvasY - this.dragging_rectangle[1], this.dirty_canvas = !0; - } else { - if (this.selected_group && !this.read_only) { - this.selected_group_resizing ? this.selected_group.size = [a.canvasX - this.selected_group.pos[0], a.canvasY - this.selected_group.pos[1]] : (this.selected_group.move(d[0] / this.ds.scale, d[1] / this.ds.scale, a.ctrlKey), this.selected_group._nodes.length && (this.dirty_canvas = !0)), this.dirty_bgcanvas = !0; - } else { - if (this.dragging_canvas) { - this.ds.offset[0] += d[0] / this.ds.scale, this.ds.offset[1] += d[1] / this.ds.scale, this.dirty_bgcanvas = this.dirty_canvas = !0; - } else { - if (this.allow_interaction && !this.read_only) { - this.connecting_node && (this.dirty_canvas = !0); - var f = this.graph.getNodeOnPos(a.canvasX, a.canvasY, this.visible_nodes); - b = 0; - for (var n = this.graph._nodes.length; b < n; ++b) { - if (this.graph._nodes[b].mouseOver && f != this.graph._nodes[b]) { - this.graph._nodes[b].mouseOver = !1; - if (this.node_over && this.node_over.onMouseLeave) { - this.node_over.onMouseLeave(a); - } - this.node_over = null; - this.dirty_canvas = !0; - } - } - if (f) { - if (!f.mouseOver && (f.mouseOver = !0, this.node_over = f, this.dirty_canvas = !0, f.onMouseEnter)) { - f.onMouseEnter(a); - } - if (f.onMouseMove) { - f.onMouseMove(a, [a.canvasX - f.pos[0], a.canvasY - f.pos[1]], this); - } - if (this.connecting_node && (n = this._highlight_input || [0, 0], !this.isOverNodeBox(f, a.canvasX, a.canvasY))) { - var c = this.isOverNodeInput(f, a.canvasX, a.canvasY, n); - -1 != c && f.inputs[c] ? e.isValidConnection(this.connecting_output.type, f.inputs[c].type) && (this._highlight_input = n) : this._highlight_input = null; - } - this.canvas && (B(a.canvasX, a.canvasY, f.pos[0] + f.size[0] - 5, f.pos[1] + f.size[1] - 5, 5, 5) ? this.canvas.style.cursor = "se-resize" : this.canvas.style.cursor = "crosshair"); - } else { - n = null; - for (b = 0; b < this.visible_links.length; ++b) { - c = this.visible_links[b]; - var l = c._pos; - if (!(!l || a.canvasX < l[0] - 4 || a.canvasX > l[0] + 4 || a.canvasY < l[1] - 4 || a.canvasY > l[1] + 4)) { - n = c; - break; - } - } - n != this.over_link_center && (this.over_link_center = n, this.dirty_canvas = !0); - this.canvas && (this.canvas.style.cursor = ""); - } - if (this.node_capturing_input && this.node_capturing_input != f && this.node_capturing_input.onMouseMove) { - this.node_capturing_input.onMouseMove(a, [a.canvasX - this.node_capturing_input.pos[0], a.canvasY - this.node_capturing_input.pos[1]], this); - } - if (this.node_dragged && !this.live_mode) { - for (b in this.selected_nodes) { - f = this.selected_nodes[b], f.pos[0] += d[0] / this.ds.scale, f.pos[1] += d[1] / this.ds.scale; - } - this.dirty_bgcanvas = this.dirty_canvas = !0; - } - this.resizing_node && !this.live_mode && (this.resizing_node.size[0] = a.canvasX - this.resizing_node.pos[0], this.resizing_node.size[1] = a.canvasY - this.resizing_node.pos[1], d = Math.max(this.resizing_node.inputs ? this.resizing_node.inputs.length : 0, this.resizing_node.outputs ? this.resizing_node.outputs.length : 0) * e.NODE_SLOT_HEIGHT + (this.resizing_node.widgets ? this.resizing_node.widgets.length : 0) * (e.NODE_WIDGET_HEIGHT + 4) + 4, this.resizing_node.size[1] < d && (this.resizing_node.size[1] = - d), this.resizing_node.size[0] < e.NODE_MIN_WIDTH && (this.resizing_node.size[0] = e.NODE_MIN_WIDTH), this.canvas.style.cursor = "se-resize", this.dirty_bgcanvas = this.dirty_canvas = !0); - } - } - } - } - a.preventDefault(); - return !1; - } - }; - g.prototype.processMouseUp = function(a) { - if (this.graph) { - var b = this.getCanvasWindow().document; - g.active_canvas = this; - b.removeEventListener("mousemove", this._mousemove_callback, !0); - this.canvas.addEventListener("mousemove", this._mousemove_callback, !0); - b.removeEventListener("mouseup", this._mouseup_callback, !0); - this.adjustMouseEvent(a); - b = e.getTime(); - a.click_time = b - this.last_mouseclick; - this.last_mouse_dragging = !1; - if (1 == a.which) { - if (this.node_widget = null, this.selected_group && (this.selected_group.move(this.selected_group.pos[0] - Math.round(this.selected_group.pos[0]), this.selected_group.pos[1] - Math.round(this.selected_group.pos[1]), a.ctrlKey), this.selected_group.pos[0] = Math.round(this.selected_group.pos[0]), this.selected_group.pos[1] = Math.round(this.selected_group.pos[1]), this.selected_group._nodes.length && (this.dirty_canvas = !0), this.selected_group = null), this.selected_group_resizing = !1, - this.dragging_rectangle) { - if (this.graph) { - b = this.graph._nodes; - var d = new Float32Array(4); - this.deselectAllNodes(); - var f = Math.abs(this.dragging_rectangle[2]), n = Math.abs(this.dragging_rectangle[3]), c = 0 > this.dragging_rectangle[3] ? this.dragging_rectangle[1] - n : this.dragging_rectangle[1]; - this.dragging_rectangle[0] = 0 > this.dragging_rectangle[2] ? this.dragging_rectangle[0] - f : this.dragging_rectangle[0]; - this.dragging_rectangle[1] = c; - this.dragging_rectangle[2] = f; - this.dragging_rectangle[3] = n; - n = []; - for (c = 0; c < b.length; ++c) { - f = b[c], f.getBounding(d), x(this.dragging_rectangle, d) && n.push(f); - } - n.length && this.selectNodes(n); - } - this.dragging_rectangle = null; - } else { - if (this.connecting_node) { - this.dirty_bgcanvas = this.dirty_canvas = !0; - if (f = this.graph.getNodeOnPos(a.canvasX, a.canvasY, this.visible_nodes)) { - this.connecting_output.type == e.EVENT && this.isOverNodeBox(f, a.canvasX, a.canvasY) ? this.connecting_node.connect(this.connecting_slot, f, e.EVENT) : (b = this.isOverNodeInput(f, a.canvasX, a.canvasY), -1 != b ? this.connecting_node.connect(this.connecting_slot, f, b) : (b = f.getInputInfo(0), this.connecting_output.type == e.EVENT ? this.connecting_node.connect(this.connecting_slot, f, e.EVENT) : b && !b.link && e.isValidConnection(b.type && this.connecting_output.type) && this.connecting_node.connect(this.connecting_slot, - f, 0))); - } - this.connecting_node = this.connecting_pos = this.connecting_output = null; - this.connecting_slot = -1; - } else { - if (this.resizing_node) { - this.dirty_bgcanvas = this.dirty_canvas = !0, this.resizing_node = null; - } else { - if (this.node_dragged) { - (f = this.node_dragged) && 300 > a.click_time && B(a.canvasX, a.canvasY, f.pos[0], f.pos[1] - e.NODE_TITLE_HEIGHT, e.NODE_TITLE_HEIGHT, e.NODE_TITLE_HEIGHT) && f.collapse(); - this.dirty_bgcanvas = this.dirty_canvas = !0; - this.node_dragged.pos[0] = Math.round(this.node_dragged.pos[0]); - this.node_dragged.pos[1] = Math.round(this.node_dragged.pos[1]); - this.graph.config.align_to_grid && this.node_dragged.alignToGrid(); - if (this.onNodeMoved) { - this.onNodeMoved(this.node_dragged); - } - this.node_dragged = null; - } else { - f = this.graph.getNodeOnPos(a.canvasX, a.canvasY, this.visible_nodes); - !f && 300 > a.click_time && this.deselectAllNodes(); - this.dirty_canvas = !0; - this.dragging_canvas = !1; - if (this.node_over && this.node_over.onMouseUp) { - this.node_over.onMouseUp(a, [a.canvasX - this.node_over.pos[0], a.canvasY - this.node_over.pos[1]], this); - } - if (this.node_capturing_input && this.node_capturing_input.onMouseUp) { - this.node_capturing_input.onMouseUp(a, [a.canvasX - this.node_capturing_input.pos[0], a.canvasY - this.node_capturing_input.pos[1]]); - } - } - } - } - } - } else { - 2 == a.which ? (this.dirty_canvas = !0, this.dragging_canvas = !1) : 3 == a.which && (this.dirty_canvas = !0, this.dragging_canvas = !1); - } - this.graph.change(); - a.stopPropagation(); - a.preventDefault(); - return !1; - } - }; - g.prototype.processMouseWheel = function(a) { - if (this.graph && this.allow_dragcanvas) { - var b = null != a.wheelDeltaY ? a.wheelDeltaY : -60 * a.detail; - this.adjustMouseEvent(a); - var d = this.ds.scale; - 0 < b ? d *= 1.1 : 0 > b && (d *= 1 / 1.1); - this.ds.changeScale(d, [a.localX, a.localY]); - this.graph.change(); - a.preventDefault(); - return !1; - } - }; - g.prototype.isOverNodeBox = function(a, b, d) { - var f = e.NODE_TITLE_HEIGHT; - return B(b, d, a.pos[0] + 2, a.pos[1] + 2 - f, f - 4, f - 4) ? !0 : !1; - }; - g.prototype.isOverNodeInput = function(a, b, d, e) { - if (a.inputs) { - for (var f = 0, c = a.inputs.length; f < c; ++f) { - var q = a.getConnectionPos(!0, f); - if (a.horizontal ? B(b, d, q[0] - 5, q[1] - 10, 10, 20) : B(b, d, q[0] - 10, q[1] - 5, 40, 10)) { - return e && (e[0] = q[0], e[1] = q[1]), f; - } - } - } - return -1; - }; - g.prototype.processKey = function(a) { - if (this.graph) { - var b = !1; - if ("input" != a.target.localName) { - if ("keydown" == a.type) { - if (32 == a.keyCode && (b = this.dragging_canvas = !0), 65 == a.keyCode && a.ctrlKey && (this.selectNodes(), b = !0), "KeyC" == a.code && (a.metaKey || a.ctrlKey) && !a.shiftKey && this.selected_nodes && (this.copyToClipboard(), b = !0), "KeyV" != a.code || !a.metaKey && !a.ctrlKey || a.shiftKey || this.pasteFromClipboard(), 46 != a.keyCode && 8 != a.keyCode || "input" == a.target.localName || "textarea" == a.target.localName || (this.deleteSelectedNodes(), b = !0), this.selected_nodes) { - for (var d in this.selected_nodes) { - if (this.selected_nodes[d].onKeyDown) { - this.selected_nodes[d].onKeyDown(a); - } - } - } - } else { - if ("keyup" == a.type && (32 == a.keyCode && (this.dragging_canvas = !1), this.selected_nodes)) { - for (d in this.selected_nodes) { - if (this.selected_nodes[d].onKeyUp) { - this.selected_nodes[d].onKeyUp(a); - } - } - } - } - this.graph.change(); - if (b) { - return a.preventDefault(), a.stopImmediatePropagation(), !1; - } - } - } - }; - g.prototype.copyToClipboard = function() { - var a = {nodes:[], links:[]}, b = 0, d = [], e; - for (e in this.selected_nodes) { - var f = this.selected_nodes[e]; - f._relative_id = b; - d.push(f); - b += 1; - } - for (e = 0; e < d.length; ++e) { - if (f = d[e], b = f.clone()) { - if (a.nodes.push(b.serialize()), f.inputs && f.inputs.length) { - for (b = 0; b < f.inputs.length; ++b) { - var c = f.inputs[b]; - if (c && null != c.link && (c = this.graph.links[c.link])) { - var g = this.graph.getNodeById(c.origin_id); - g && this.selected_nodes[g.id] && a.links.push([g._relative_id, c.origin_slot, f._relative_id, c.target_slot]); - } - } - } - } else { - console.warn("node type not found: " + f.type); - } - } - localStorage.setItem("litegrapheditor_clipboard", JSON.stringify(a)); - }; - g.prototype.pasteFromClipboard = function() { - var a = localStorage.getItem("litegrapheditor_clipboard"); - if (a) { - a = JSON.parse(a); - for (var b = [], d = 0; d < a.nodes.length; ++d) { - var f = a.nodes[d], c = e.createNode(f.type); - c && (c.configure(f), c.pos[0] += 5, c.pos[1] += 5, this.graph.add(c), b.push(c)); - } - for (d = 0; d < a.links.length; ++d) { - f = a.links[d]; - c = b[f[0]]; - var g = b[f[2]]; - c && g ? c.connect(f[1], g, f[3]) : console.warn("Warning, nodes missing on pasting"); - } - this.selectNodes(b); - } - }; - g.prototype.processDrop = function(a) { - a.preventDefault(); - this.adjustMouseEvent(a); - var b = [a.canvasX, a.canvasY], d = this.graph.getNodeOnPos(b[0], b[1]); - if (d) { - if ((d.onDropFile || d.onDropData) && (b = a.dataTransfer.files) && b.length) { - for (var e = 0; e < b.length; e++) { - var f = a.dataTransfer.files[0], c = f.name; - g.getFileExtension(c); - if (d.onDropFile) { - d.onDropFile(f); - } - if (d.onDropData) { - var l = new FileReader; - l.onload = function(a) { - d.onDropData(a.target.result, c, f); - }; - var p = f.type.split("/")[0]; - "text" == p || "" == p ? l.readAsText(f) : "image" == p ? l.readAsDataURL(f) : l.readAsArrayBuffer(f); - } - } - } - return d.onDropItem && d.onDropItem(event) ? !0 : this.onDropItem ? this.onDropItem(event) : !1; - } - b = null; - this.onDropItem && (b = this.onDropItem(event)); - b || this.checkDropItem(a); - }; - g.prototype.checkDropItem = function(a) { - if (a.dataTransfer.files.length) { - var b = a.dataTransfer.files[0], d = g.getFileExtension(b.name).toLowerCase(); - if (d = e.node_types_by_file_extension[d]) { - if (d = e.createNode(d.type), d.pos = [a.canvasX, a.canvasY], this.graph.add(d), d.onDropFile) { - d.onDropFile(b); - } - } - } - }; - g.prototype.processNodeDblClicked = function(a) { - if (this.onShowNodePanel) { - this.onShowNodePanel(a); - } - if (this.onNodeDblClicked) { - this.onNodeDblClicked(a); - } - this.setDirty(!0); - }; - g.prototype.processNodeSelected = function(a, b) { - this.selectNode(a, b && b.shiftKey); - if (this.onNodeSelected) { - this.onNodeSelected(a); - } - }; - g.prototype.selectNode = function(a, b) { - null == a ? this.deselectAllNodes() : this.selectNodes([a], b); - }; - g.prototype.selectNodes = function(a, b) { - b || this.deselectAllNodes(); - a = a || this.graph._nodes; - for (b = 0; b < a.length; ++b) { - var d = a[b]; - if (!d.is_selected) { - if (!d.is_selected && d.onSelected) { - d.onSelected(); - } - d.is_selected = !0; - this.selected_nodes[d.id] = d; - if (d.inputs) { - for (var e = 0; e < d.inputs.length; ++e) { - this.highlighted_links[d.inputs[e].link] = !0; - } - } - if (d.outputs) { - for (e = 0; e < d.outputs.length; ++e) { - var f = d.outputs[e]; - if (f.links) { - for (var c = 0; c < f.links.length; ++c) { - this.highlighted_links[f.links[c]] = !0; - } - } - } - } - } - } - if (this.onSelectionChange) { - this.onSelectionChange(this.selected_nodes); - } - this.setDirty(!0); - }; - g.prototype.deselectNode = function(a) { - if (a.is_selected) { - if (a.onDeselected) { - a.onDeselected(); - } - a.is_selected = !1; - if (this.onNodeDeselected) { - this.onNodeDeselected(a); - } - if (a.inputs) { - for (var b = 0; b < a.inputs.length; ++b) { - delete this.highlighted_links[a.inputs[b].link]; - } - } - if (a.outputs) { - for (b = 0; b < a.outputs.length; ++b) { - var d = a.outputs[b]; - if (d.links) { - for (var e = 0; e < d.links.length; ++e) { - delete this.highlighted_links[d.links[e]]; - } - } - } - } - } - }; - g.prototype.deselectAllNodes = function() { - if (this.graph) { - for (var a = this.graph._nodes, b = 0, d = a.length; b < d; ++b) { - var e = a[b]; - if (e.is_selected) { - if (e.onDeselected) { - e.onDeselected(); - } - e.is_selected = !1; - if (this.onNodeDeselected) { - this.onNodeDeselected(e); - } - } - } - this.selected_nodes = {}; - this.current_node = null; - this.highlighted_links = {}; - if (this.onSelectionChange) { - this.onSelectionChange(this.selected_nodes); - } - this.setDirty(!0); - } - }; - g.prototype.deleteSelectedNodes = function() { - for (var a in this.selected_nodes) { - var b = this.selected_nodes[a]; - if (b.inputs && b.inputs.length && b.outputs && b.outputs.length && e.isValidConnection(b.inputs[0].type, b.outputs[0].type) && b.inputs[0].link && b.outputs[0].links && b.outputs[0].links.length) { - var d = b.graph.links[b.inputs[0].link], f = b.graph.links[b.outputs[0].links[0]], c = b.getInputNode(0), g = b.getOutputNodes(0)[0]; - c && g && c.connect(d.origin_slot, g, f.target_slot); - } - this.graph.remove(b); - if (this.onNodeDeselected) { - this.onNodeDeselected(b); - } - } - this.selected_nodes = {}; - this.current_node = null; - this.highlighted_links = {}; - this.setDirty(!0); - }; - g.prototype.centerOnNode = function(a) { - this.ds.offset[0] = -a.pos[0] - 0.5 * a.size[0] + 0.5 * this.canvas.width / this.ds.scale; - this.ds.offset[1] = -a.pos[1] - 0.5 * a.size[1] + 0.5 * this.canvas.height / this.ds.scale; - this.setDirty(!0, !0); - }; - g.prototype.adjustMouseEvent = function(a) { - if (this.canvas) { - var b = this.canvas.getBoundingClientRect(); - a.localX = a.clientX - b.left; - a.localY = a.clientY - b.top; - } else { - a.localX = a.clientX, a.localY = a.clientY; - } - a.deltaX = a.localX - this.last_mouse_position[0]; - a.deltaY = a.localY - this.last_mouse_position[1]; - this.last_mouse_position[0] = a.localX; - this.last_mouse_position[1] = a.localY; - a.canvasX = a.localX / this.ds.scale - this.ds.offset[0]; - a.canvasY = a.localY / this.ds.scale - this.ds.offset[1]; - }; - g.prototype.setZoom = function(a, b) { - this.ds.changeScale(a, b); - this.dirty_bgcanvas = this.dirty_canvas = !0; - }; - g.prototype.convertOffsetToCanvas = function(a, b) { - return this.ds.convertOffsetToCanvas(a, b); - }; - g.prototype.convertCanvasToOffset = function(a, b) { - return this.ds.convertCanvasToOffset(a, b); - }; - g.prototype.convertEventToCanvasOffset = function(a) { - var b = this.canvas.getBoundingClientRect(); - return this.convertCanvasToOffset([a.clientX - b.left, a.clientY - b.top]); - }; - g.prototype.bringToFront = function(a) { - var b = this.graph._nodes.indexOf(a); - -1 != b && (this.graph._nodes.splice(b, 1), this.graph._nodes.push(a)); - }; - g.prototype.sendToBack = function(a) { - var b = this.graph._nodes.indexOf(a); - -1 != b && (this.graph._nodes.splice(b, 1), this.graph._nodes.unshift(a)); - }; - var y = new Float32Array(4); - g.prototype.computeVisibleNodes = function(a, b) { - b = b || []; - b.length = 0; - a = a || this.graph._nodes; - for (var d = 0, e = a.length; d < e; ++d) { - var f = a[d]; - (!this.live_mode || f.onDrawBackground || f.onDrawForeground) && x(this.visible_area, f.getBounding(y)) && b.push(f); - } - return b; - }; - g.prototype.draw = function(a, b) { - if (this.canvas) { - var d = e.getTime(); - this.render_time = 0.001 * (d - this.last_draw_time); - this.last_draw_time = d; - this.graph && this.ds.computeVisibleArea(); - (this.dirty_bgcanvas || b || this.always_render_background || this.graph && this.graph._last_trigger_time && 1000 > d - this.graph._last_trigger_time) && this.drawBackCanvas(); - (this.dirty_canvas || a) && this.drawFrontCanvas(); - this.fps = this.render_time ? 1.0 / this.render_time : 0; - this.frame += 1; - } - }; - g.prototype.drawFrontCanvas = function() { - this.dirty_canvas = !1; - this.ctx || (this.ctx = this.bgcanvas.getContext("2d")); - var a = this.ctx; - if (a) { - a.start2D && a.start2D(); - var b = this.canvas; - a.restore(); - a.setTransform(1, 0, 0, 1, 0, 0); - this.dirty_area && (a.save(), a.beginPath(), a.rect(this.dirty_area[0], this.dirty_area[1], this.dirty_area[2], this.dirty_area[3]), a.clip()); - this.clear_background && a.clearRect(0, 0, b.width, b.height); - this.bgcanvas == this.canvas ? this.drawBackCanvas() : a.drawImage(this.bgcanvas, 0, 0); - if (this.onRender) { - this.onRender(b, a); - } - this.show_info && this.renderInfo(a); - if (this.graph) { - a.save(); - this.ds.toCanvasContext(a); - b = this.computeVisibleNodes(null, this.visible_nodes); - for (var d = 0; d < b.length; ++d) { - var f = b[d]; - a.save(); - a.translate(f.pos[0], f.pos[1]); - this.drawNode(f, a); - a.restore(); - } - this.render_execution_order && this.drawExecutionOrder(a); - this.graph.config.links_ontop && (this.live_mode || this.drawConnections(a)); - if (null != this.connecting_pos) { - a.lineWidth = this.connections_width; - switch(this.connecting_output.type) { - case e.EVENT: - b = e.EVENT_LINK_COLOR; - break; - default: - b = e.CONNECTING_LINK_COLOR; - } - this.renderLink(a, this.connecting_pos, [this.canvas_mouse[0], this.canvas_mouse[1]], null, !1, null, b, this.connecting_output.dir || (this.connecting_node.horizontal ? e.DOWN : e.RIGHT), e.CENTER); - a.beginPath(); - this.connecting_output.type === e.EVENT || this.connecting_output.shape === e.BOX_SHAPE ? a.rect(this.connecting_pos[0] - 6 + 0.5, this.connecting_pos[1] - 5 + 0.5, 14, 10) : a.arc(this.connecting_pos[0], this.connecting_pos[1], 4, 0, 2 * Math.PI); - a.fill(); - a.fillStyle = "#ffcc00"; - this._highlight_input && (a.beginPath(), a.arc(this._highlight_input[0], this._highlight_input[1], 6, 0, 2 * Math.PI), a.fill()); - } - this.dragging_rectangle && (a.strokeStyle = "#FFF", a.strokeRect(this.dragging_rectangle[0], this.dragging_rectangle[1], this.dragging_rectangle[2], this.dragging_rectangle[3])); - if (this.over_link_center && this.render_link_tooltip) { - this.drawLinkTooltip(a, this.over_link_center); - } else { - if (this.onDrawLinkTooltip) { - this.onDrawLinkTooltip(a, null); - } - } - if (this.onDrawForeground) { - this.onDrawForeground(a, this.visible_rect); - } - a.restore(); - } - if (this.onDrawOverlay) { - this.onDrawOverlay(a); - } - this.dirty_area && a.restore(); - a.finish2D && a.finish2D(); - } - }; - g.prototype.renderInfo = function(a, b, d) { - b = b || 0; - d = d || 0; - a.save(); - a.translate(b, d); - a.font = "10px Arial"; - a.fillStyle = "#888"; - this.graph ? (a.fillText("T: " + this.graph.globaltime.toFixed(2) + "s", 5, 13), a.fillText("I: " + this.graph.iteration, 5, 26), a.fillText("N: " + this.graph._nodes.length + " [" + this.visible_nodes.length + "]", 5, 39), a.fillText("V: " + this.graph._version, 5, 52), a.fillText("FPS:" + this.fps.toFixed(2), 5, 65)) : a.fillText("No graph selected", 5, 13); - a.restore(); - }; - g.prototype.drawBackCanvas = function() { - var a = this.bgcanvas; - if (a.width != this.canvas.width || a.height != this.canvas.height) { - a.width = this.canvas.width, a.height = this.canvas.height; - } - this.bgctx || (this.bgctx = this.bgcanvas.getContext("2d")); - var b = this.bgctx; - b.start && b.start(); - this.clear_background && b.clearRect(0, 0, a.width, a.height); - if (this._graph_stack && this._graph_stack.length) { - b.save(); - var d = this.graph._subgraph_node; - b.strokeStyle = d.bgcolor; - b.lineWidth = 10; - b.strokeRect(1, 1, a.width - 2, a.height - 2); - b.lineWidth = 1; - b.font = "40px Arial"; - b.textAlign = "center"; - b.fillStyle = d.bgcolor || "#AAA"; - for (var e = "", f = 1; f < this._graph_stack.length; ++f) { - e += this._graph_stack[f]._subgraph_node.getTitle() + " >> "; - } - b.fillText(e + d.getTitle(), 0.5 * a.width, 40); - b.restore(); - } - d = !1; - this.onRenderBackground && (d = this.onRenderBackground(a, b)); - b.restore(); - b.setTransform(1, 0, 0, 1, 0, 0); - this.visible_links.length = 0; - if (this.graph) { - b.save(); - this.ds.toCanvasContext(b); - if (this.background_image && 0.5 < this.ds.scale && !d) { - b.globalAlpha = this.zoom_modify_alpha ? (1.0 - 0.5 / this.ds.scale) * this.editor_alpha : this.editor_alpha; - b.imageSmoothingEnabled = b.mozImageSmoothingEnabled = b.imageSmoothingEnabled = !1; - if (!this._bg_img || this._bg_img.name != this.background_image) { - this._bg_img = new Image; - this._bg_img.name = this.background_image; - this._bg_img.src = this.background_image; - var c = this; - this._bg_img.onload = function() { - c.draw(!0, !0); - }; - } - d = null; - null == this._pattern && 0 < this._bg_img.width ? (d = b.createPattern(this._bg_img, "repeat"), this._pattern_img = this._bg_img, this._pattern = d) : d = this._pattern; - d && (b.fillStyle = d, b.fillRect(this.visible_area[0], this.visible_area[1], this.visible_area[2], this.visible_area[3]), b.fillStyle = "transparent"); - b.globalAlpha = 1.0; - b.imageSmoothingEnabled = b.mozImageSmoothingEnabled = b.imageSmoothingEnabled = !0; - } - this.graph._groups.length && !this.live_mode && this.drawGroups(a, b); - if (this.onDrawBackground) { - this.onDrawBackground(b, this.visible_area); - } - this.onBackgroundRender && (console.error("WARNING! onBackgroundRender deprecated, now is named onDrawBackground "), this.onBackgroundRender = null); - this.render_canvas_border && (b.strokeStyle = "#235", b.strokeRect(0, 0, a.width, a.height)); - this.render_connections_shadows ? (b.shadowColor = "#000", b.shadowOffsetX = 0, b.shadowOffsetY = 0, b.shadowBlur = 6) : b.shadowColor = "rgba(0,0,0,0)"; - this.live_mode || this.drawConnections(b); - b.shadowColor = "rgba(0,0,0,0)"; - b.restore(); - } - b.finish && b.finish(); - this.dirty_bgcanvas = !1; - this.dirty_canvas = !0; - }; - var w = new Float32Array(2); - g.prototype.drawNode = function(a, b) { - this.current_node = a; - var d = a.color || a.constructor.color || e.NODE_DEFAULT_COLOR, f = a.bgcolor || a.constructor.bgcolor || e.NODE_DEFAULT_BGCOLOR, c = 0.6 > this.ds.scale; - if (this.live_mode) { - if (!a.flags.collapsed && (b.shadowColor = "transparent", a.onDrawForeground)) { - a.onDrawForeground(b, this, this.canvas); - } - } else { - var g = this.editor_alpha; - b.globalAlpha = g; - this.render_shadows && !c ? (b.shadowColor = e.DEFAULT_SHADOW_COLOR, b.shadowOffsetX = 2 * this.ds.scale, b.shadowOffsetY = 2 * this.ds.scale, b.shadowBlur = 3 * this.ds.scale) : b.shadowColor = "transparent"; - if (!a.flags.collapsed || !a.onDrawCollapsed || 1 != a.onDrawCollapsed(b, this)) { - var l = a._shape || e.BOX_SHAPE; - w.set(a.size); - var p = a.horizontal; - if (a.flags.collapsed) { - b.font = this.inner_text_font; - var m = a.getTitle ? a.getTitle() : a.title; - null != m && (a._collapsed_width = Math.min(a.size[0], b.measureText(m).width + 2 * e.NODE_TITLE_HEIGHT), w[0] = a._collapsed_width, w[1] = 0); - } - a.clip_area && (b.save(), b.beginPath(), l == e.BOX_SHAPE ? b.rect(0, 0, w[0], w[1]) : l == e.ROUND_SHAPE ? b.roundRect(0, 0, w[0], w[1], 10) : l == e.CIRCLE_SHAPE && b.arc(0.5 * w[0], 0.5 * w[1], 0.5 * w[0], 0, 2 * Math.PI), b.clip()); - a.has_errors && (f = "red"); - this.drawNodeShape(a, b, w, d, f, a.is_selected, a.mouseOver); - b.shadowColor = "transparent"; - if (a.onDrawForeground) { - a.onDrawForeground(b, this, this.canvas); - } - b.textAlign = p ? "center" : "left"; - b.font = this.inner_text_font; - f = !c; - l = this.connecting_output; - b.lineWidth = 1; - m = 0; - var h = new Float32Array(2); - if (!a.flags.collapsed) { - if (a.inputs) { - for (d = 0; d < a.inputs.length; d++) { - var u = a.inputs[d]; - b.globalAlpha = g; - this.connecting_node && !e.isValidConnection(u.type, l.type) && (b.globalAlpha = 0.4 * g); - b.fillStyle = null != u.link ? u.color_on || this.default_connection_color.input_on : u.color_off || this.default_connection_color.input_off; - var C = a.getConnectionPos(!0, d, h); - C[0] -= a.pos[0]; - C[1] -= a.pos[1]; - m < C[1] + 0.5 * e.NODE_SLOT_HEIGHT && (m = C[1] + 0.5 * e.NODE_SLOT_HEIGHT); - b.beginPath(); - u.type === e.EVENT || u.shape === e.BOX_SHAPE ? p ? b.rect(C[0] - 5 + 0.5, C[1] - 8 + 0.5, 10, 14) : b.rect(C[0] - 6 + 0.5, C[1] - 5 + 0.5, 14, 10) : u.shape === e.ARROW_SHAPE ? (b.moveTo(C[0] + 8, C[1] + 0.5), b.lineTo(C[0] - 4, C[1] + 6 + 0.5), b.lineTo(C[0] - 4, C[1] - 6 + 0.5), b.closePath()) : c ? b.rect(C[0] - 4, C[1] - 4, 8, 8) : b.arc(C[0], C[1], 4, 0, 2 * Math.PI); - b.fill(); - if (f) { - var r = null != u.label ? u.label : u.name; - r && (b.fillStyle = e.NODE_TEXT_COLOR, p || u.dir == e.UP ? b.fillText(r, C[0], C[1] - 10) : b.fillText(r, C[0] + 10, C[1] + 5)); - } - } - } - this.connecting_node && (b.globalAlpha = 0.4 * g); - b.textAlign = p ? "center" : "right"; - b.strokeStyle = "black"; - if (a.outputs) { - for (d = 0; d < a.outputs.length; d++) { - if (u = a.outputs[d], C = a.getConnectionPos(!1, d, h), C[0] -= a.pos[0], C[1] -= a.pos[1], m < C[1] + 0.5 * e.NODE_SLOT_HEIGHT && (m = C[1] + 0.5 * e.NODE_SLOT_HEIGHT), b.fillStyle = u.links && u.links.length ? u.color_on || this.default_connection_color.output_on : u.color_off || this.default_connection_color.output_off, b.beginPath(), u.type === e.EVENT || u.shape === e.BOX_SHAPE ? p ? b.rect(C[0] - 5 + 0.5, C[1] - 8 + 0.5, 10, 14) : b.rect(C[0] - 6 + 0.5, C[1] - 5 + 0.5, 14, 10) : - u.shape === e.ARROW_SHAPE ? (b.moveTo(C[0] + 8, C[1] + 0.5), b.lineTo(C[0] - 4, C[1] + 6 + 0.5), b.lineTo(C[0] - 4, C[1] - 6 + 0.5), b.closePath()) : c ? b.rect(C[0] - 4, C[1] - 4, 8, 8) : b.arc(C[0], C[1], 4, 0, 2 * Math.PI), b.fill(), c || b.stroke(), f && (r = null != u.label ? u.label : u.name)) { - b.fillStyle = e.NODE_TEXT_COLOR, p || u.dir == e.DOWN ? b.fillText(r, C[0], C[1] - 8) : b.fillText(r, C[0] - 10, C[1] + 5); - } - } - } - b.textAlign = "left"; - b.globalAlpha = 1; - if (a.widgets) { - u = m; - if (p || a.widgets_up) { - u = 2; - } - null != a.widgets_start_y && (u = a.widgets_start_y); - this.drawNodeWidgets(a, u, b, this.node_widget && this.node_widget[0] == a ? this.node_widget[1] : null); - } - } else { - if (this.render_collapsed_slots) { - c = g = null; - if (a.inputs) { - for (d = 0; d < a.inputs.length; d++) { - if (u = a.inputs[d], null != u.link) { - g = u; - break; - } - } - } - if (a.outputs) { - for (d = 0; d < a.outputs.length; d++) { - u = a.outputs[d], u.links && u.links.length && (c = u); - } - } - g && (g = 0, d = -0.5 * e.NODE_TITLE_HEIGHT, p && (g = 0.5 * a._collapsed_width, d = -e.NODE_TITLE_HEIGHT), b.fillStyle = "#686", b.beginPath(), u.type === e.EVENT || u.shape === e.BOX_SHAPE ? b.rect(g - 7 + 0.5, d - 4, 14, 8) : u.shape === e.ARROW_SHAPE ? (b.moveTo(g + 8, d), b.lineTo(g + -4, d - 4), b.lineTo(g + -4, d + 4), b.closePath()) : b.arc(g, d, 4, 0, 2 * Math.PI), b.fill()); - c && (g = a._collapsed_width, d = -0.5 * e.NODE_TITLE_HEIGHT, p && (g = 0.5 * a._collapsed_width, d = 0), b.fillStyle = "#686", b.strokeStyle = "black", b.beginPath(), u.type === e.EVENT || u.shape === e.BOX_SHAPE ? b.rect(g - 7 + 0.5, d - 4, 14, 8) : u.shape === e.ARROW_SHAPE ? (b.moveTo(g + 6, d), b.lineTo(g - 6, d - 4), b.lineTo(g - 6, d + 4), b.closePath()) : b.arc(g, d, 4, 0, 2 * Math.PI), b.fill()); - } - } - a.clip_area && b.restore(); - b.globalAlpha = 1.0; - } - } - }; - g.prototype.drawLinkTooltip = function(a, b) { - var d = b._pos; - a.fillStyle = "black"; - a.beginPath(); - a.arc(d[0], d[1], 3, 0, 2 * Math.PI); - a.fill(); - if (null != b.data && (!this.onDrawLinkTooltip || 1 != this.onDrawLinkTooltip(a, b, this)) && (b = b.data, b = b.constructor === Number ? b.toFixed(2) : b.constructor === String ? '"' + b + '"' : b.constructor === Boolean ? String(b) : b.toToolTip ? b.toToolTip() : "[" + b.constructor.name + "]", null != b)) { - a.font = "14px Courier New"; - var e = a.measureText(b).width + 20; - a.shadowColor = "black"; - a.shadowOffsetX = 2; - a.shadowOffsetY = 2; - a.shadowBlur = 3; - a.fillStyle = "#454"; - a.beginPath(); - a.roundRect(d[0] - 0.5 * e, d[1] - 15 - 24, e, 24, 3, 3); - a.moveTo(d[0] - 10, d[1] - 15); - a.lineTo(d[0] + 10, d[1] - 15); - a.lineTo(d[0], d[1] - 5); - a.fill(); - a.shadowColor = "transparent"; - a.textAlign = "center"; - a.fillStyle = "#CEC"; - a.fillText(b, d[0], d[1] - 15 - 24 * 0.3); - } - }; - var z = new Float32Array(4); - g.prototype.drawNodeShape = function(a, b, d, f, c, l, p) { - b.strokeStyle = f; - b.fillStyle = c; - c = e.NODE_TITLE_HEIGHT; - var n = 0.5 > this.ds.scale, q = a._shape || a.constructor.shape || e.ROUND_SHAPE, m = a.constructor.title_mode, h = !0; - m == e.TRANSPARENT_TITLE ? h = !1 : m == e.AUTOHIDE_TITLE && p && (h = !0); - z[0] = 0; - z[1] = h ? -c : 0; - z[2] = d[0] + 1; - z[3] = h ? d[1] + c : d[1]; - p = b.globalAlpha; - b.beginPath(); - q == e.BOX_SHAPE || n ? b.fillRect(z[0], z[1], z[2], z[3]) : q == e.ROUND_SHAPE || q == e.CARD_SHAPE ? b.roundRect(z[0], z[1], z[2], z[3], this.round_radius, q == e.CARD_SHAPE ? 0 : this.round_radius) : q == e.CIRCLE_SHAPE && b.arc(0.5 * d[0], 0.5 * d[1], 0.5 * d[0], 0, 2 * Math.PI); - b.fill(); - a.flags.collapsed || (b.shadowColor = "transparent", b.fillStyle = "rgba(0,0,0,0.2)", b.fillRect(0, -1, z[2], 2)); - b.shadowColor = "transparent"; - if (a.onDrawBackground) { - a.onDrawBackground(b, this, this.canvas); - } - if (h || m == e.TRANSPARENT_TITLE) { - if (a.onDrawTitleBar) { - a.onDrawTitleBar(b, c, d, this.ds.scale, f); - } else { - if (m != e.TRANSPARENT_TITLE && (a.constructor.title_color || this.render_title_colored)) { - h = a.constructor.title_color || f; - a.flags.collapsed && (b.shadowColor = e.DEFAULT_SHADOW_COLOR); - if (this.use_gradients) { - var G = g.gradients[h]; - G || (G = g.gradients[h] = b.createLinearGradient(0, 0, 400, 0), G.addColorStop(0, h), G.addColorStop(1, "#000")); - b.fillStyle = G; - } else { - b.fillStyle = h; - } - b.beginPath(); - q == e.BOX_SHAPE || n ? b.rect(0, -c, d[0] + 1, c) : (q == e.ROUND_SHAPE || q == e.CARD_SHAPE) && b.roundRect(0, -c, d[0] + 1, c, this.round_radius, a.flags.collapsed ? this.round_radius : 0); - b.fill(); - b.shadowColor = "transparent"; - } - } - if (a.onDrawTitleBox) { - a.onDrawTitleBox(b, c, d, this.ds.scale); - } else { - q == e.ROUND_SHAPE || q == e.CIRCLE_SHAPE || q == e.CARD_SHAPE ? (n && (b.fillStyle = "black", b.beginPath(), b.arc(0.5 * c, -0.5 * c, 6, 0, 2 * Math.PI), b.fill()), b.fillStyle = a.boxcolor || e.NODE_DEFAULT_BOXCOLOR, n ? b.fillRect(0.5 * c - 5, -0.5 * c - 5, 10, 10) : (b.beginPath(), b.arc(0.5 * c, -0.5 * c, 5, 0, 2 * Math.PI), b.fill())) : (n && (b.fillStyle = "black", b.fillRect(0.5 * (c - 10) - 1, -0.5 * (c + 10) - 1, 12, 12)), b.fillStyle = a.boxcolor || e.NODE_DEFAULT_BOXCOLOR, b.fillRect(0.5 * - (c - 10), -0.5 * (c + 10), 10, 10)); - } - b.globalAlpha = p; - if (a.onDrawTitleText) { - a.onDrawTitleText(b, c, d, this.ds.scale, this.title_text_font, l); - } - !n && (b.font = this.title_text_font, n = a.getTitle()) && (b.fillStyle = l ? "white" : a.constructor.title_text_color || this.node_title_color, a.flags.collapsed ? (b.textAlign = "center", p = b.measureText(n), b.fillText(n, c + 0.5 * p.width, e.NODE_TITLE_TEXT_Y - c), b.textAlign = "left") : (b.textAlign = "left", b.fillText(n, c, e.NODE_TITLE_TEXT_Y - c))); - if (a.onDrawTitle) { - a.onDrawTitle(b); - } - } - if (l) { - if (a.onBounding) { - a.onBounding(z); - } - m == e.TRANSPARENT_TITLE && (z[1] -= c, z[3] += c); - b.lineWidth = 1; - b.globalAlpha = 0.8; - b.beginPath(); - q == e.BOX_SHAPE ? b.rect(-6 + z[0], -6 + z[1], 12 + z[2], 12 + z[3]) : q == e.ROUND_SHAPE || q == e.CARD_SHAPE && a.flags.collapsed ? b.roundRect(-6 + z[0], -6 + z[1], 12 + z[2], 12 + z[3], 2 * this.round_radius) : q == e.CARD_SHAPE ? b.roundRect(-6 + z[0], -6 + z[1], 12 + z[2], 12 + z[3], 2 * this.round_radius, 2) : q == e.CIRCLE_SHAPE && b.arc(0.5 * d[0], 0.5 * d[1], 0.5 * d[0] + 6, 0, 2 * Math.PI); - b.strokeStyle = "#FFF"; - b.stroke(); - b.strokeStyle = f; - b.globalAlpha = 1; - } - }; - var l = new Float32Array(4), p = new Float32Array(4), r = new Float32Array(2), f = new Float32Array(2); - g.prototype.drawConnections = function(a) { - var b = e.getTime(), d = this.visible_area; - l[0] = d[0] - 20; - l[1] = d[1] - 20; - l[2] = d[2] + 40; - l[3] = d[3] + 40; - a.lineWidth = this.connections_width; - a.fillStyle = "#AAA"; - a.strokeStyle = "#AAA"; - a.globalAlpha = this.editor_alpha; - d = this.graph._nodes; - for (var c = 0, n = d.length; c < n; ++c) { - var g = d[c]; - if (g.inputs && g.inputs.length) { - for (var m = 0; m < g.inputs.length; ++m) { - var h = g.inputs[m]; - if (h && null != h.link && (h = this.graph.links[h.link])) { - var t = this.graph.getNodeById(h.origin_id); - if (null != t) { - var k = h.origin_slot; - var u = -1 == k ? [t.pos[0] + 10, t.pos[1] + 10] : t.getConnectionPos(!1, k, r); - var C = g.getConnectionPos(!0, m, f); - p[0] = u[0]; - p[1] = u[1]; - p[2] = C[0] - u[0]; - p[3] = C[1] - u[1]; - 0 > p[2] && (p[0] += p[2], p[2] = Math.abs(p[2])); - 0 > p[3] && (p[1] += p[3], p[3] = Math.abs(p[3])); - if (x(p, l)) { - var y = t.outputs[k]; - k = g.inputs[m]; - if (y && k && (t = y.dir || (t.horizontal ? e.DOWN : e.RIGHT), k = k.dir || (g.horizontal ? e.UP : e.LEFT), this.renderLink(a, u, C, h, !1, 0, null, t, k), h && h._last_time && 1000 > b - h._last_time)) { - y = 2.0 - 0.002 * (b - h._last_time); - var w = a.globalAlpha; - a.globalAlpha = w * y; - this.renderLink(a, u, C, h, !0, y, "white", t, k); - a.globalAlpha = w; - } - } - } - } - } - } - } - a.globalAlpha = 1; - }; - g.prototype.renderLink = function(a, b, d, f, c, l, p, h, m, r) { - f && this.visible_links.push(f); - !p && f && (p = f.color || g.link_type_colors[f.type]); - p || (p = this.default_link_color); - null != f && this.highlighted_links[f.id] && (p = "#FFF"); - h = h || e.RIGHT; - m = m || e.LEFT; - var n = D(b, d); - this.render_connections_border && 0.6 < this.ds.scale && (a.lineWidth = this.connections_width + 4); - a.lineJoin = "round"; - r = r || 1; - 1 < r && (a.lineWidth = 0.5); - a.beginPath(); - for (var q = 0; q < r; q += 1) { - var G = 5 * (q - 0.5 * (r - 1)); - if (this.links_render_mode == e.SPLINE_LINK) { - a.moveTo(b[0], b[1] + G); - var t = 0, k = 0, I = 0, H = 0; - switch(h) { - case e.LEFT: - t = -0.25 * n; - break; - case e.RIGHT: - t = 0.25 * n; - break; - case e.UP: - k = -0.25 * n; - break; - case e.DOWN: - k = 0.25 * n; - } - switch(m) { - case e.LEFT: - I = -0.25 * n; - break; - case e.RIGHT: - I = 0.25 * n; - break; - case e.UP: - H = -0.25 * n; - break; - case e.DOWN: - H = 0.25 * n; - } - a.bezierCurveTo(b[0] + t, b[1] + k + G, d[0] + I, d[1] + H + G, d[0], d[1] + G); - } else { - if (this.links_render_mode == e.LINEAR_LINK) { - a.moveTo(b[0], b[1] + G); - H = I = k = t = 0; - switch(h) { - case e.LEFT: - t = -1; - break; - case e.RIGHT: - t = 1; - break; - case e.UP: - k = -1; - break; - case e.DOWN: - k = 1; - } - switch(m) { - case e.LEFT: - I = -1; - break; - case e.RIGHT: - I = 1; - break; - case e.UP: - H = -1; - break; - case e.DOWN: - H = 1; - } - a.lineTo(b[0] + 15 * t, b[1] + 15 * k + G); - a.lineTo(d[0] + 15 * I, d[1] + 15 * H + G); - a.lineTo(d[0], d[1] + G); - } else { - if (this.links_render_mode == e.STRAIGHT_LINK) { - a.moveTo(b[0], b[1]), G = b[0], t = b[1], k = d[0], I = d[1], h == e.RIGHT ? G += 10 : t += 10, m == e.LEFT ? k -= 10 : I -= 10, a.lineTo(G, t), a.lineTo(0.5 * (G + k), t), a.lineTo(0.5 * (G + k), I), a.lineTo(k, I), a.lineTo(d[0], d[1]); - } else { - return; - } - } - } - } - this.render_connections_border && 0.6 < this.ds.scale && !c && (a.strokeStyle = "rgba(0,0,0,0.5)", a.stroke()); - a.lineWidth = this.connections_width; - a.fillStyle = a.strokeStyle = p; - a.stroke(); - c = this.computeConnectionPoint(b, d, 0.5, h, m); - f && f._pos && (f._pos[0] = c[0], f._pos[1] = c[1]); - 0.6 <= this.ds.scale && this.highquality_render && m != e.CENTER && (this.render_connection_arrows && (q = this.computeConnectionPoint(b, d, 0.25, h, m), n = this.computeConnectionPoint(b, d, 0.26, h, m), f = this.computeConnectionPoint(b, d, 0.75, h, m), r = this.computeConnectionPoint(b, d, 0.76, h, m), this.render_curved_connections ? (n = -Math.atan2(n[0] - q[0], n[1] - q[1]), r = -Math.atan2(r[0] - f[0], r[1] - f[1])) : r = n = d[1] > b[1] ? 0 : Math.PI, a.save(), a.translate(q[0], q[1]), - a.rotate(n), a.beginPath(), a.moveTo(-5, -3), a.lineTo(0, 7), a.lineTo(5, -3), a.fill(), a.restore(), a.save(), a.translate(f[0], f[1]), a.rotate(r), a.beginPath(), a.moveTo(-5, -3), a.lineTo(0, 7), a.lineTo(5, -3), a.fill(), a.restore()), a.beginPath(), a.arc(c[0], c[1], 5, 0, 2 * Math.PI), a.fill()); - if (l) { - for (a.fillStyle = p, q = 0; 5 > q; ++q) { - l = (0.001 * e.getTime() + 0.2 * q) % 1, c = this.computeConnectionPoint(b, d, l, h, m), a.beginPath(), a.arc(c[0], c[1], 5, 0, 2 * Math.PI), a.fill(); - } - } - }; - g.prototype.computeConnectionPoint = function(a, b, d, f, c) { - f = f || e.RIGHT; - c = c || e.LEFT; - var n = D(a, b), g = [a[0], a[1]], l = [b[0], b[1]]; - switch(f) { - case e.LEFT: - g[0] += -0.25 * n; - break; - case e.RIGHT: - g[0] += 0.25 * n; - break; - case e.UP: - g[1] += -0.25 * n; - break; - case e.DOWN: - g[1] += 0.25 * n; - } - switch(c) { - case e.LEFT: - l[0] += -0.25 * n; - break; - case e.RIGHT: - l[0] += 0.25 * n; - break; - case e.UP: - l[1] += -0.25 * n; - break; - case e.DOWN: - l[1] += 0.25 * n; - } - f = (1 - d) * (1 - d) * (1 - d); - c = 3 * (1 - d) * (1 - d) * d; - n = 3 * (1 - d) * d * d; - d *= d * d; - return [f * a[0] + c * g[0] + n * l[0] + d * b[0], f * a[1] + c * g[1] + n * l[1] + d * b[1]]; - }; - g.prototype.drawExecutionOrder = function(a) { - a.shadowColor = "transparent"; - a.globalAlpha = 0.25; - a.textAlign = "center"; - a.strokeStyle = "white"; - a.globalAlpha = 0.75; - for (var b = this.visible_nodes, d = 0; d < b.length; ++d) { - var f = b[d]; - a.fillStyle = "black"; - a.fillRect(f.pos[0] - e.NODE_TITLE_HEIGHT, f.pos[1] - e.NODE_TITLE_HEIGHT, e.NODE_TITLE_HEIGHT, e.NODE_TITLE_HEIGHT); - 0 == f.order && a.strokeRect(f.pos[0] - e.NODE_TITLE_HEIGHT + 0.5, f.pos[1] - e.NODE_TITLE_HEIGHT + 0.5, e.NODE_TITLE_HEIGHT, e.NODE_TITLE_HEIGHT); - a.fillStyle = "#FFF"; - a.fillText(f.order, f.pos[0] + -0.5 * e.NODE_TITLE_HEIGHT, f.pos[1] - 6); - } - a.globalAlpha = 1; - }; - g.prototype.drawNodeWidgets = function(a, b, d, f) { - if (!a.widgets || !a.widgets.length) { - return 0; - } - var c = a.size[0], g = a.widgets; - b += 2; - var l = e.NODE_WIDGET_HEIGHT, p = 0.5 < this.ds.scale; - d.save(); - d.globalAlpha = this.editor_alpha; - for (var q = e.WIDGET_OUTLINE_COLOR, h = e.WIDGET_BGCOLOR, m = e.WIDGET_TEXT_COLOR, r = e.WIDGET_SECONDARY_TEXT_COLOR, t = 0; t < g.length; ++t) { - var k = g[t], y = b; - k.y && (y = k.y); - k.last_y = y; - d.strokeStyle = q; - d.fillStyle = "#222"; - d.textAlign = "left"; - k.disabled && (d.globalAlpha *= 0.5); - switch(k.type) { - case "button": - k.clicked && (d.fillStyle = "#AAA", k.clicked = !1, this.dirty_canvas = !0); - d.fillRect(15, y, c - 30, l); - p && d.strokeRect(15, y, c - 30, l); - p && (d.textAlign = "center", d.fillStyle = m, d.fillText(k.name, 0.5 * c, y + 0.7 * l)); - break; - case "toggle": - d.textAlign = "left"; - d.strokeStyle = q; - d.fillStyle = h; - d.beginPath(); - p ? d.roundRect(15, b, c - 30, l, 0.5 * l) : d.rect(15, b, c - 30, l); - d.fill(); - p && d.stroke(); - d.fillStyle = k.value ? "#89A" : "#333"; - d.beginPath(); - d.arc(c - 30, y + 0.5 * l, 0.36 * l, 0, 2 * Math.PI); - d.fill(); - p && (d.fillStyle = r, null != k.name && d.fillText(k.name, 30, y + 0.7 * l), d.fillStyle = k.value ? m : r, d.textAlign = "right", d.fillText(k.value ? k.options.on || "true" : k.options.off || "false", c - 40, y + 0.7 * l)); - break; - case "slider": - d.fillStyle = h; - d.fillRect(15, y, c - 30, l); - var I = k.options.max - k.options.min, P = (k.value - k.options.min) / I; - d.fillStyle = f == k ? "#89A" : "#678"; - d.fillRect(15, y, P * (c - 30), l); - p && d.strokeRect(15, y, c - 30, l); - k.marker && (I = (k.marker - k.options.min) / I, d.fillStyle = "#AA9", d.fillRect(15 + I * (c - 30), y, 2, l)); - p && (d.textAlign = "center", d.fillStyle = m, d.fillText(k.name + " " + Number(k.value).toFixed(3), 0.5 * c, y + 0.7 * l)); - break; - case "number": - case "combo": - d.textAlign = "left"; - d.strokeStyle = q; - d.fillStyle = h; - d.beginPath(); - p ? d.roundRect(15, b, c - 30, l, 0.5 * l) : d.rect(15, b, c - 30, l); - d.fill(); - p && (d.stroke(), d.fillStyle = m, d.beginPath(), d.moveTo(31, b + 5), d.lineTo(21, b + 0.5 * l), d.lineTo(31, b + l - 5), d.fill(), d.beginPath(), d.moveTo(c - 15 - 16, b + 5), d.lineTo(c - 15 - 6, b + 0.5 * l), d.lineTo(c - 15 - 16, b + l - 5), d.fill(), d.fillStyle = r, d.fillText(k.name, 35, y + 0.7 * l), d.fillStyle = m, d.textAlign = "right", "number" == k.type ? d.fillText(Number(k.value).toFixed(void 0 !== k.options.precision ? k.options.precision : 3), c - 30 - 20, y + 0.7 * l) : - d.fillText(k.value, c - 30 - 20, y + 0.7 * l)); - break; - case "string": - case "text": - d.textAlign = "left"; - d.strokeStyle = q; - d.fillStyle = h; - d.beginPath(); - p ? d.roundRect(15, b, c - 30, l, 0.5 * l) : d.rect(15, b, c - 30, l); - d.fill(); - p && (d.stroke(), d.fillStyle = r, null != k.name && d.fillText(k.name, 30, y + 0.7 * l), d.fillStyle = m, d.textAlign = "right", d.fillText(k.value, c - 30, y + 0.7 * l)); - break; - default: - k.draw && k.draw(d, a, k, y, l); - } - b += l + 4; - d.globalAlpha = this.editor_alpha; - } - d.restore(); - d.textAlign = "left"; - }; - g.prototype.processNodeWidgets = function(a, b, d, f) { - function c(f, e) { - f.value = e; - f.options && f.options.property && void 0 !== a.properties[f.options.property] && a.setProperty(f.options.property, e); - f.callback && f.callback(f.value, q, a, b, d); - } - if (!a.widgets || !a.widgets.length) { - return null; - } - for (var g = b[0] - a.pos[0], l = b[1] - a.pos[1], p = a.size[0], q = this, m = this.getCanvasWindow(), h = 0; h < a.widgets.length; ++h) { - var k = a.widgets[h]; - if (!k.disabled && (k == f || 6 < g && g < p - 12 && l > k.last_y && l < k.last_y + e.NODE_WIDGET_HEIGHT)) { - switch(k.type) { - case "button": - if ("mousemove" === d.type) { - break; - } - k.callback && setTimeout(function() { - k.callback(k, q, a, b, d); - }, 20); - this.dirty_canvas = k.clicked = !0; - break; - case "slider": - m = Math.clamp((g - 10) / (p - 20), 0, 1); - k.value = k.options.min + (k.options.max - k.options.min) * m; - k.callback && setTimeout(function() { - c(k, k.value); - }, 20); - this.dirty_canvas = !0; - break; - case "number": - case "combo": - f = k.value; - "mousemove" == d.type && "number" == k.type ? (k.value += 0.1 * d.deltaX * (k.options.step || 1), null != k.options.min && k.value < k.options.min && (k.value = k.options.min), null != k.options.max && k.value > k.options.max && (k.value = k.options.max)) : "mousedown" == d.type && ((l = k.options.values) && l.constructor === Function && (l = k.options.values(k, a)), g = 40 > g ? -1 : g > p - 40 ? 1 : 0, "number" == k.type ? (k.value += 0.1 * g * (k.options.step || 1), null != k.options.min && - k.value < k.options.min && (k.value = k.options.min), null != k.options.max && k.value > k.options.max && (k.value = k.options.max)) : g ? (m = l.indexOf(k.value) + g, m >= l.length && (m = 0), 0 > m && (m = l.length - 1), k.value = l[m]) : new e.ContextMenu(l, {scale:Math.max(1, this.ds.scale), event:d, className:"dark", callback:function(a, b, d) { - this.value = a; - c(this, a); - q.dirty_canvas = !0; - return !1; - }.bind(k)}, m)); - f != k.value && setTimeout(function() { - c(this, this.value); - }.bind(k), 20); - this.dirty_canvas = !0; - break; - case "toggle": - "mousedown" == d.type && (k.value = !k.value, setTimeout(function() { - c(k, k.value); - }, 20)); - break; - case "string": - case "text": - "mousedown" == d.type && this.prompt("Value", k.value, function(a) { - this.value = a; - c(this, a); - }.bind(k), d); - break; - default: - k.mouse && k.mouse(ctx, d, [g, l], a); - } - return k; - } - } - return null; - }; - g.prototype.drawGroups = function(a, b) { - if (this.graph) { - a = this.graph._groups; - b.save(); - b.globalAlpha = 0.5 * this.editor_alpha; - for (var d = 0; d < a.length; ++d) { - var f = a[d]; - if (x(this.visible_area, f._bounding)) { - b.fillStyle = f.color || "#335"; - b.strokeStyle = f.color || "#335"; - var c = f._pos, g = f._size; - b.globalAlpha = 0.25 * this.editor_alpha; - b.beginPath(); - b.rect(c[0] + 0.5, c[1] + 0.5, g[0], g[1]); - b.fill(); - b.globalAlpha = this.editor_alpha; - b.stroke(); - b.beginPath(); - b.moveTo(c[0] + g[0], c[1] + g[1]); - b.lineTo(c[0] + g[0] - 10, c[1] + g[1]); - b.lineTo(c[0] + g[0], c[1] + g[1] - 10); - b.fill(); - g = f.font_size || e.DEFAULT_GROUP_FONT_SIZE; - b.font = g + "px Arial"; - b.fillText(f.title, c[0] + 4, c[1] + g); - } - } - b.restore(); - } - }; - g.prototype.adjustNodesSize = function() { - for (var a = this.graph._nodes, b = 0; b < a.length; ++b) { - a[b].size = a[b].computeSize(); - } - this.setDirty(!0, !0); - }; - g.prototype.resize = function(a, b) { - a || b || (b = this.canvas.parentNode, a = b.offsetWidth, b = b.offsetHeight); - if (this.canvas.width != a || this.canvas.height != b) { - this.canvas.width = a, this.canvas.height = b, this.bgcanvas.width = this.canvas.width, this.bgcanvas.height = this.canvas.height, this.setDirty(!0, !0); - } - }; - g.prototype.switchLiveMode = function(a) { - if (a) { - var b = this, d = this.live_mode ? 1.1 : 0.9; - this.live_mode && (this.live_mode = !1, this.editor_alpha = 0.1); - var f = setInterval(function() { - b.editor_alpha *= d; - b.dirty_canvas = !0; - b.dirty_bgcanvas = !0; - 1 > d && 0.01 > b.editor_alpha && (clearInterval(f), 1 > d && (b.live_mode = !0)); - 1 < d && 0.99 < b.editor_alpha && (clearInterval(f), b.editor_alpha = 1); - }, 1); - } else { - this.live_mode = !this.live_mode, this.dirty_bgcanvas = this.dirty_canvas = !0; - } - }; - g.prototype.onNodeSelectionChange = function(a) { - }; - g.prototype.touchHandler = function(a) { - var b = a.changedTouches[0]; - switch(a.type) { - case "touchstart": - var d = "mousedown"; - break; - case "touchmove": - d = "mousemove"; - break; - case "touchend": - d = "mouseup"; - break; - default: - return; - } - var f = this.getCanvasWindow(), e = f.document.createEvent("MouseEvent"); - e.initMouseEvent(d, !0, !0, f, 1, b.screenX, b.screenY, b.clientX, b.clientY, !1, !1, !1, !1, 0, null); - b.target.dispatchEvent(e); - a.preventDefault(); - }; - g.onGroupAdd = function(a, b, d) { - a = g.active_canvas; - a.getCanvasWindow(); - b = new e.LGraphGroup; - b.pos = a.convertEventToCanvasOffset(d); - a.graph.add(b); - }; - g.onMenuAdd = function(a, b, d, f, c) { - function n(a, b) { - b = f.getFirstEvent(); - if (a = e.createNode(a.value)) { - a.pos = l.convertEventToCanvasOffset(b), l.graph.add(a); - } - c && c(a); - } - var l = g.active_canvas, p = l.getCanvasWindow(); - a = e.getNodeTypesCategories(l.filter); - b = []; - for (var m = 0; m < a.length; m++) { - a[m] && b.push({value:a[m], content:a[m], has_submenu:!0}); - } - var h = new e.ContextMenu(b, {event:d, callback:function(a, b, d) { - a = e.getNodeTypesInCategory(a.value, l.filter); - b = []; - for (var f = 0; f < a.length; f++) { - a[f].skip_list || b.push({content:a[f].title, value:a[f].type}); - } - new e.ContextMenu(b, {event:d, callback:n, parentMenu:h}, p); - return !1; - }, parentMenu:f}, p); - return !1; - }; - g.onMenuCollapseAll = function() { - }; - g.onMenuNodeEdit = function() { - }; - g.showMenuNodeOptionalInputs = function(a, b, d, f, c) { - if (c) { - var l = this; - a = g.active_canvas.getCanvasWindow(); - b = c.optional_inputs; - c.onGetInputs && (b = c.onGetInputs()); - var n = []; - if (b) { - for (var p = 0; p < b.length; p++) { - var m = b[p]; - if (m) { - var h = m[0]; - m[2] && m[2].label && (h = m[2].label); - h = {content:h, value:m}; - m[1] == e.ACTION && (h.className = "event"); - n.push(h); - } else { - n.push(null); - } - } - } - this.onMenuNodeInputs && (n = this.onMenuNodeInputs(n)); - if (n.length) { - return new e.ContextMenu(n, {event:d, callback:function(a, b, d) { - c && (a.callback && a.callback.call(l, c, a, b, d), a.value && (c.addInput(a.value[0], a.value[1], a.value[2]), c.setDirtyCanvas(!0, !0))); - }, parentMenu:f, node:c}, a), !1; - } - } - }; - g.showMenuNodeOptionalOutputs = function(a, b, d, f, c) { - function l(a, b, d) { - if (c && (a.callback && a.callback.call(n, c, a, b, d), a.value)) { - if (d = a.value[1], !d || d.constructor !== Object && d.constructor !== Array) { - c.addOutput(a.value[0], a.value[1], a.value[2]), c.setDirtyCanvas(!0, !0); - } else { - a = []; - for (var g in d) { - a.push({content:g, value:d[g]}); - } - new e.ContextMenu(a, {event:b, callback:l, parentMenu:f, node:c}); - return !1; - } - } - } - if (c) { - var n = this; - a = g.active_canvas.getCanvasWindow(); - b = c.optional_outputs; - c.onGetOutputs && (b = c.onGetOutputs()); - var p = []; - if (b) { - for (var m = 0; m < b.length; m++) { - var h = b[m]; - if (!h) { - p.push(null); - } else { - if (!c.flags || !c.flags.skip_repeated_outputs || -1 == c.findOutputSlot(h[0])) { - var q = h[0]; - h[2] && h[2].label && (q = h[2].label); - q = {content:q, value:h}; - h[1] == e.EVENT && (q.className = "event"); - p.push(q); - } - } - } - } - this.onMenuNodeOutputs && (p = this.onMenuNodeOutputs(p)); - if (p.length) { - return new e.ContextMenu(p, {event:d, callback:l, parentMenu:f, node:c}, a), !1; - } - } - }; - g.onShowMenuNodeProperties = function(a, b, d, f, c) { - if (c && c.properties) { - var l = g.active_canvas; - b = l.getCanvasWindow(); - var n = [], p; - for (p in c.properties) { - a = void 0 !== c.properties[p] ? c.properties[p] : " ", "object" == typeof a && (a = JSON.stringify(a)), a = g.decodeHTML(a), n.push({content:"" + p + "" + a + "", value:p}); - } - if (n.length) { - return new e.ContextMenu(n, {event:d, callback:function(a, b, d, f) { - c && (b = this.getBoundingClientRect(), l.showEditPropertyValue(c, a.value, {position:[b.left, b.top]})); - }, parentMenu:f, allow_html:!0, node:c}, b), !1; - } - } - }; - g.decodeHTML = function(a) { - var b = document.createElement("div"); - b.innerText = a; - return b.innerHTML; - }; - g.onResizeNode = function(a, b, d, f, e) { - e && (e.size = e.computeSize(), e.setDirtyCanvas(!0, !0)); - }; - g.prototype.showLinkMenu = function(a, b) { - var d = this; - console.log(a); - var f = new e.ContextMenu(["Add Node", null, "Delete"], {event:b, title:null != a.data ? a.data.constructor.name : null, callback:function(b, e, c) { - switch(b) { - case "Add Node": - g.onMenuAdd(null, null, c, f, function(b) { - console.log("node autoconnect"); - var f = d.graph.getNodeById(a.origin_id), e = d.graph.getNodeById(a.target_id); - b.inputs && b.inputs.length && b.outputs && b.outputs.length && f.outputs[a.origin_slot].type == b.inputs[0].type && b.outputs[0].type == e.inputs[0].type && (f.connect(a.origin_slot, b, 0), b.connect(0, e, a.target_slot), b.pos[0] -= 0.5 * b.size[0]); - }); - break; - case "Delete": - d.graph.removeLink(a.id); - } - }}); - return !1; - }; - g.onShowPropertyEditor = function(a, b, d, f, e) { - function c() { - var b = p.value; - "Number" == a.type ? b = Number(b) : "Boolean" == a.type && (b = !!b); - e[l] = b; - n.parentNode && n.parentNode.removeChild(n); - e.setDirtyCanvas(!0, !0); - } - var l = a.property || "title"; - b = e[l]; - var n = document.createElement("div"); - n.className = "graphdialog"; - n.innerHTML = ""; - n.querySelector(".name").innerText = l; - var p = n.querySelector("input"); - p && (p.value = b, p.addEventListener("blur", function(a) { - this.focus(); - }), p.addEventListener("keydown", function(a) { - 13 == a.keyCode && (c(), a.preventDefault(), a.stopPropagation()); - })); - b = g.active_canvas.canvas; - d = b.getBoundingClientRect(); - var m = f = -20; - d && (f -= d.left, m -= d.top); - event ? (n.style.left = event.clientX + f + "px", n.style.top = event.clientY + m + "px") : (n.style.left = 0.5 * b.width + f + "px", n.style.top = 0.5 * b.height + m + "px"); - n.querySelector("button").addEventListener("click", c); - b.parentNode.appendChild(n); - }; - g.prototype.prompt = function(a, b, d, f) { - var e = this; - a = a || ""; - var c = !1, l = document.createElement("div"); - l.className = "graphdialog rounded"; - l.innerHTML = " "; - l.close = function() { - e.prompt_box = null; - l.parentNode && l.parentNode.removeChild(l); - }; - 1 < this.ds.scale && (l.style.transform = "scale(" + this.ds.scale + ")"); - l.addEventListener("mouseleave", function(a) { - c || l.close(); - }); - e.prompt_box && e.prompt_box.close(); - e.prompt_box = l; - l.querySelector(".name").innerText = a; - l.querySelector(".value").value = b; - var p = l.querySelector("input"); - p.addEventListener("keydown", function(a) { - c = !0; - if (27 == a.keyCode) { - l.close(); - } else { - if (13 == a.keyCode) { - d && d(this.value), l.close(); - } else { - return; - } - } - a.preventDefault(); - a.stopPropagation(); - }); - l.querySelector("button").addEventListener("click", function(a) { - d && d(p.value); - e.setDirty(!0); - l.close(); - }); - a = g.active_canvas.canvas; - b = a.getBoundingClientRect(); - var m = -20, h = -20; - b && (m -= b.left, h -= b.top); - f ? (l.style.left = f.clientX + m + "px", l.style.top = f.clientY + h + "px") : (l.style.left = 0.5 * a.width + m + "px", l.style.top = 0.5 * a.height + h + "px"); - a.parentNode.appendChild(l); - setTimeout(function() { - p.focus(); - }, 10); - return l; - }; - g.search_limit = -1; - g.prototype.showSearchBox = function(a) { - function b(b) { - if (b) { - if (c.onSearchBoxSelection) { - c.onSearchBoxSelection(b, a, l); - } else { - var d = e.searchbox_extras[b.toLowerCase()]; - d && (b = d.type); - if (b = e.createNode(b)) { - b.pos = l.convertEventToCanvasOffset(a), l.graph.add(b); - } - if (d && d.data) { - if (d.data.properties) { - for (var f in d.data.properties) { - b.addProperty(f, d.data.properties[f]); - } - } - if (d.data.inputs) { - for (f in b.inputs = [], d.data.inputs) { - b.addOutput(d.data.inputs[f][0], d.data.inputs[f][1]); - } - } - if (d.data.outputs) { - for (f in b.outputs = [], d.data.outputs) { - b.addOutput(d.data.outputs[f][0], d.data.outputs[f][1]); - } - } - d.data.title && (b.title = d.data.title); - d.data.json && b.configure(d.data.json); - } - } - } - h.close(); - } - function d(a) { - var b = w; - w && w.classList.remove("selected"); - w ? (w = a ? w.nextSibling : w.previousSibling) || (w = b) : w = a ? r.childNodes[0] : r.childNodes[r.childNodes.length]; - w && (w.classList.add("selected"), w.scrollIntoView({block:"end", behavior:"smooth"})); - } - function f() { - function a(a, d) { - var f = document.createElement("div"); - t || (t = a); - f.innerText = a; - f.dataset.type = escape(a); - f.className = "litegraph lite-search-item"; - d && (f.className += " " + d); - f.addEventListener("click", function(a) { - b(unescape(this.dataset.type)); - }); - r.appendChild(f); - } - y = null; - var d = x.value; - t = null; - r.innerHTML = ""; - if (d) { - if (c.onSearchBox) { - var f = c.onSearchBox(r, d, l); - if (f) { - for (var n = 0; n < f.length; ++n) { - a(f[n]); - } - } - } else { - f = function(a) { - var b = e.registered_node_types[a]; - return h && b.filter != h ? !1 : -1 !== a.toLowerCase().indexOf(d); - }; - var p = 0; - d = d.toLowerCase(); - var h = l.filter || l.graph.filter; - for (n in e.searchbox_extras) { - var m = e.searchbox_extras[n]; - if (-1 !== m.desc.toLowerCase().indexOf(d)) { - var k = e.registered_node_types[m.type]; - if (!k || !k.filter || k.filter == h) { - if (a(m.desc, "searchbox_extra"), -1 !== g.search_limit && p++ > g.search_limit) { - break; - } - } - } - } - m = null; - if (Array.prototype.filter) { - m = Object.keys(e.registered_node_types).filter(f); - } else { - for (n in m = [], e.registered_node_types) { - f(n) && m.push(n); - } - } - for (n = 0; n < m.length && !(a(m[n]), -1 !== g.search_limit && p++ > g.search_limit); n++) { - } - } - } - } - var c = this, l = g.active_canvas, p = l.canvas, m = p.ownerDocument || document, h = document.createElement("div"); - h.className = "litegraph litesearchbox graphdialog rounded"; - h.innerHTML = "Search
"; - h.close = function() { - c.search_box = null; - m.body.focus(); - m.body.style.overflow = ""; - setTimeout(function() { - c.canvas.focus(); - }, 20); - h.parentNode && h.parentNode.removeChild(h); - }; - var k = null; - 1 < this.ds.scale && (h.style.transform = "scale(" + this.ds.scale + ")"); - h.addEventListener("mouseenter", function(a) { - k && (clearTimeout(k), k = null); - }); - h.addEventListener("mouseleave", function(a) { - k = setTimeout(function() { - h.close(); - }, 500); - }); - c.search_box && c.search_box.close(); - c.search_box = h; - var r = h.querySelector(".helper"), t = null, y = null, w = null, x = h.querySelector("input"); - x && (x.addEventListener("blur", function(a) { - this.focus(); - }), x.addEventListener("keydown", function(a) { - if (38 == a.keyCode) { - d(!1); - } else { - if (40 == a.keyCode) { - d(!0); - } else { - if (27 == a.keyCode) { - h.close(); - } else { - if (13 == a.keyCode) { - w ? b(w.innerHTML) : t ? b(t) : h.close(); - } else { - y && clearInterval(y); - y = setTimeout(f, 10); - return; - } - } - } - } - a.preventDefault(); - a.stopPropagation(); - a.stopImmediatePropagation(); - return !0; - })); - m.fullscreenElement ? m.fullscreenElement.appendChild(h) : (m.body.appendChild(h), m.body.style.overflow = "hidden"); - p = p.getBoundingClientRect(); - var I = (a ? a.clientY : p.top + 0.5 * p.height) - 20; - h.style.left = (a ? a.clientX : p.left + 0.5 * p.width) - 80 + "px"; - h.style.top = I + "px"; - a.layerY > p.height - 200 && (r.style.maxHeight = p.height - a.layerY - 20 + "px"); - x.focus(); - return h; - }; - g.prototype.showEditPropertyValue = function(a, b, d) { - function f() { - e(k.value); - } - function e(d) { - "number" == typeof a.properties[b] && (d = Number(d)); - if ("array" == c || "object" == c) { - d = JSON.parse(d); - } - a.properties[b] = d; - a._graph && a._graph._version++; - if (a.onPropertyChanged) { - a.onPropertyChanged(b, d); - } - h.close(); - a.setDirtyCanvas(!0, !0); - } - if (a && void 0 !== a.properties[b]) { - d = d || {}; - var c = "string"; - null !== a.properties[b] && (c = typeof a.properties[b]); - var l = null; - a.getPropertyInfo && (l = a.getPropertyInfo(b)); - if (a.properties_info) { - for (var g = 0; g < a.properties_info.length; ++g) { - if (a.properties_info[g].name == b) { - l = a.properties_info[g]; - break; - } - } - } - void 0 !== l && null !== l && l.type && (c = l.type); - var p = ""; - if ("string" == c || "number" == c || "array" == c || "object" == c) { - p = ""; - } else { - if ("enum" == c && l.values) { - p = ""; - } else { - if ("boolean" == c) { - p = ""; - } else { - console.warn("unknown type: " + c); - return; - } - } - } - var h = this.createDialog("" + b + "" + p + "", d); - if ("enum" == c && l.values) { - var k = h.querySelector("select"); - k.addEventListener("change", function(a) { - e(a.target.value); - }); - } else { - if ("boolean" == c) { - (k = h.querySelector("input")) && k.addEventListener("click", function(a) { - e(!!k.checked); - }); - } else { - if (k = h.querySelector("input")) { - k.addEventListener("blur", function(a) { - this.focus(); - }), m = void 0 !== a.properties[b] ? a.properties[b] : "", m = JSON.stringify(m), k.value = m, k.addEventListener("keydown", function(a) { - 13 == a.keyCode && (f(), a.preventDefault(), a.stopPropagation()); - }); - } - } - } - h.querySelector("button").addEventListener("click", f); - } - }; - g.prototype.createDialog = function(a, b) { - b = b || {}; - var d = document.createElement("div"); - d.className = "graphdialog"; - d.innerHTML = a; - a = this.canvas.getBoundingClientRect(); - var f = -20, e = -20; - a && (f -= a.left, e -= a.top); - b.position ? (f += b.position[0], e += b.position[1]) : b.event ? (f += b.event.clientX, e += b.event.clientY) : (f += 0.5 * this.canvas.width, e += 0.5 * this.canvas.height); - d.style.left = f + "px"; - d.style.top = e + "px"; - this.canvas.parentNode.appendChild(d); - d.close = function() { - this.parentNode && this.parentNode.removeChild(this); - }; - return d; - }; - g.onMenuNodeCollapse = function(a, b, d, f, e) { - e.collapse(); - }; - g.onMenuNodePin = function(a, b, d, f, e) { - e.pin(); - }; - g.onMenuNodeMode = function(a, b, d, f, c) { - new e.ContextMenu(["Always", "On Event", "On Trigger", "Never"], {event:d, callback:function(a) { - if (c) { - switch(a) { - case "On Event": - c.mode = e.ON_EVENT; - break; - case "On Trigger": - c.mode = e.ON_TRIGGER; - break; - case "Never": - c.mode = e.NEVER; - break; - default: - c.mode = e.ALWAYS; - } - } - }, parentMenu:f, node:c}); - return !1; - }; - g.onMenuNodeColors = function(a, b, d, f, c) { - if (!c) { - throw "no node for color"; - } - b = []; - b.push({value:null, content:"No color"}); - for (var l in g.node_colors) { - a = g.node_colors[l], a = {value:l, content:"" + l + ""}, b.push(a); - } - new e.ContextMenu(b, {event:d, callback:function(a) { - c && ((a = a.value ? g.node_colors[a.value] : null) ? c.constructor === e.LGraphGroup ? c.color = a.groupcolor : (c.color = a.color, c.bgcolor = a.bgcolor) : (delete c.color, delete c.bgcolor), c.setDirtyCanvas(!0, !0)); - }, parentMenu:f, node:c}); - return !1; - }; - g.onMenuNodeShapes = function(a, b, d, f, c) { - if (!c) { - throw "no node passed"; - } - new e.ContextMenu(e.VALID_SHAPES, {event:d, callback:function(a) { - c && (c.shape = a, c.setDirtyCanvas(!0)); - }, parentMenu:f, node:c}); - return !1; - }; - g.onMenuNodeRemove = function(a, b, d, f, e) { - if (!e) { - throw "no node passed"; - } - !1 !== e.removable && (e.graph.remove(e), e.setDirtyCanvas(!0, !0)); - }; - g.onMenuNodeClone = function(a, b, d, f, e) { - 0 != e.clonable && (a = e.clone()) && (a.pos = [e.pos[0] + 5, e.pos[1] + 5], e.graph.add(a), e.setDirtyCanvas(!0, !0)); - }; - g.node_colors = {red:{color:"#322", bgcolor:"#533", groupcolor:"#A88"}, brown:{color:"#332922", bgcolor:"#593930", groupcolor:"#b06634"}, green:{color:"#232", bgcolor:"#353", groupcolor:"#8A8"}, blue:{color:"#223", bgcolor:"#335", groupcolor:"#88A"}, pale_blue:{color:"#2a363b", bgcolor:"#3f5159", groupcolor:"#3f789e"}, cyan:{color:"#233", bgcolor:"#355", groupcolor:"#8AA"}, purple:{color:"#323", bgcolor:"#535", groupcolor:"#a1309b"}, yellow:{color:"#432", bgcolor:"#653", groupcolor:"#b58b2a"}, - black:{color:"#222", bgcolor:"#000", groupcolor:"#444"}}; - g.prototype.getCanvasMenuOptions = function() { - if (this.getMenuOptions) { - var a = this.getMenuOptions(); - } else { - a = [{content:"Add Node", has_submenu:!0, callback:g.onMenuAdd}, {content:"Add Group", callback:g.onGroupAdd}], this._graph_stack && 0 < this._graph_stack.length && a.push(null, {content:"Close subgraph", callback:this.closeSubgraph.bind(this)}); - } - if (this.getExtraMenuOptions) { - var b = this.getExtraMenuOptions(this, a); - b && (a = a.concat(b)); - } - return a; - }; - g.prototype.getNodeMenuOptions = function(a) { - var b = a.getMenuOptions ? a.getMenuOptions(this) : [{content:"Inputs", has_submenu:!0, disabled:!0, callback:g.showMenuNodeOptionalInputs}, {content:"Outputs", has_submenu:!0, disabled:!0, callback:g.showMenuNodeOptionalOutputs}, null, {content:"Properties", has_submenu:!0, callback:g.onShowMenuNodeProperties}, null, {content:"Title", callback:g.onShowPropertyEditor}, {content:"Mode", has_submenu:!0, callback:g.onMenuNodeMode}, {content:"Resize", callback:g.onResizeNode}, {content:"Collapse", - callback:g.onMenuNodeCollapse}, {content:"Pin", callback:g.onMenuNodePin}, {content:"Colors", has_submenu:!0, callback:g.onMenuNodeColors}, {content:"Shapes", has_submenu:!0, callback:g.onMenuNodeShapes}, null]; - if (a.onGetInputs) { - var d = a.onGetInputs(); - d && d.length && (b[0].disabled = !1); - } - a.onGetOutputs && (d = a.onGetOutputs()) && d.length && (b[1].disabled = !1); - a.getExtraMenuOptions && (d = a.getExtraMenuOptions(this)) && (d.push(null), b = d.concat(b)); - !1 !== a.clonable && b.push({content:"Clone", callback:g.onMenuNodeClone}); - !1 !== a.removable && b.push(null, {content:"Remove", callback:g.onMenuNodeRemove}); - if (a.graph && a.graph.onGetNodeMenuOptions) { - a.graph.onGetNodeMenuOptions(b, a); - } - return b; - }; - g.prototype.getGroupMenuOptions = function(a) { - return [{content:"Title", callback:g.onShowPropertyEditor}, {content:"Color", has_submenu:!0, callback:g.onMenuNodeColors}, {content:"Font size", property:"font_size", type:"Number", callback:g.onShowPropertyEditor}, null, {content:"Remove", callback:g.onMenuNodeRemove}]; - }; - g.prototype.processContextMenu = function(a, b) { - var d = this, f = g.active_canvas.getCanvasWindow(), c = null, l = {event:b, callback:function(b, f, e) { - if (b) { - if ("Remove Slot" == b.content) { - b = b.slot, b.input ? a.removeInput(b.slot) : b.output && a.removeOutput(b.slot); - } else { - if ("Disconnect Links" == b.content) { - b = b.slot, b.output ? a.disconnectOutput(b.slot) : b.input && a.disconnectInput(b.slot); - } else { - if ("Rename Slot" == b.content) { - b = b.slot; - var c = b.input ? a.getInputInfo(b.slot) : a.getOutputInfo(b.slot), l = d.createDialog("Name", f), g = l.querySelector("input"); - g && c && (g.value = c.label || ""); - l.querySelector("button").addEventListener("click", function(a) { - g.value && (c && (c.label = g.value), d.setDirty(!0)); - l.close(); - }); - } - } - } - } - }, extra:a}; - a && (l.title = a.type); - var p = null; - a && (p = a.getSlotInPosition(b.canvasX, b.canvasY), g.active_node = a); - p ? (c = [], p && p.output && p.output.links && p.output.links.length && c.push({content:"Disconnect Links", slot:p}), b = p.input || p.output, c.push(b.locked ? "Cannot remove" : {content:"Remove Slot", slot:p}), c.push(b.nameLocked ? "Cannot rename" : {content:"Rename Slot", slot:p}), l.title = (p.input ? p.input.type : p.output.type) || "*", p.input && p.input.type == e.ACTION && (l.title = "Action"), p.output && p.output.type == e.EVENT && (l.title = "Event")) : a ? c = this.getNodeMenuOptions(a) : - (c = this.getCanvasMenuOptions(), (p = this.graph.getGroupOnPos(b.canvasX, b.canvasY)) && c.push(null, {content:"Edit Group", has_submenu:!0, submenu:{title:"Group", extra:p, options:this.getGroupMenuOptions(p)}})); - c && new e.ContextMenu(c, l, f); - }; - this.CanvasRenderingContext2D && (CanvasRenderingContext2D.prototype.roundRect = function(a, b, d, f, e, c) { - void 0 === e && (e = 5); - void 0 === c && (c = e); - this.moveTo(a + e, b); - this.lineTo(a + d - e, b); - this.quadraticCurveTo(a + d, b, a + d, b + e); - this.lineTo(a + d, b + f - c); - this.quadraticCurveTo(a + d, b + f, a + d - c, b + f); - this.lineTo(a + c, b + f); - this.quadraticCurveTo(a, b + f, a, b + f - c); - this.lineTo(a, b + e); - this.quadraticCurveTo(a, b, a + e, b); - }); - e.compareObjects = function(a, b) { - for (var d in a) { - if (a[d] != b[d]) { - return !1; - } - } - return !0; - }; - e.distance = D; - e.colorToString = function(a) { - return "rgba(" + Math.round(255 * a[0]).toFixed() + "," + Math.round(255 * a[1]).toFixed() + "," + Math.round(255 * a[2]).toFixed() + "," + (4 == a.length ? a[3].toFixed(2) : "1.0") + ")"; - }; - e.isInsideRectangle = B; - e.growBounding = function(a, b, d) { - b < a[0] ? a[0] = b : b > a[2] && (a[2] = b); - d < a[1] ? a[1] = d : d > a[3] && (a[3] = d); - }; - e.isInsideBounding = function(a, b) { - return a[0] < b[0][0] || a[1] < b[0][1] || a[0] > b[1][0] || a[1] > b[1][1] ? !1 : !0; - }; - e.overlapBounding = x; - e.hex2num = function(a) { - "#" == a.charAt(0) && (a = a.slice(1)); - a = a.toUpperCase(); - for (var b = Array(3), d = 0, f, e, c = 0; 6 > c; c += 2) { - f = "0123456789ABCDEF".indexOf(a.charAt(c)), e = "0123456789ABCDEF".indexOf(a.charAt(c + 1)), b[d] = 16 * f + e, d++; - } - return b; - }; - e.num2hex = function(a) { - for (var b = "#", d, f, e = 0; 3 > e; e++) { - d = a[e] / 16, f = a[e] % 16, b += "0123456789ABCDEF".charAt(d) + "0123456789ABCDEF".charAt(f); - } - return b; - }; - E.prototype.addItem = function(a, b, d) { - function f(a) { - var b = this.value; - b && b.has_submenu && e.call(this, a); - } - function e(a) { - var b = this.value, f = !0; - c.current_submenu && c.current_submenu.close(a); - if (d.callback) { - var e = d.callback.call(this, b, d, a, c, d.node); - !0 === e && (f = !1); - } - if (b && (b.callback && !d.ignore_item_callbacks && !0 !== b.disabled && (e = b.callback.call(this, b, d, a, c, d.extra), !0 === e && (f = !1)), b.submenu)) { - if (!b.submenu.options) { - throw "ContextMenu submenu needs options"; - } - new c.constructor(b.submenu.options, {callback:b.submenu.callback, event:a, parentMenu:c, ignore_item_callbacks:b.submenu.ignore_item_callbacks, title:b.submenu.title, extra:b.submenu.extra, autoopen:d.autoopen}); - f = !1; - } - f && !c.lock && c.close(); - } - var c = this; - d = d || {}; - var l = document.createElement("div"); - l.className = "litemenu-entry submenu"; - var g = !1; - if (null === b) { - l.classList.add("separator"); - } else { - l.innerHTML = b && b.title ? b.title : a; - if (l.value = b) { - b.disabled && (g = !0, l.classList.add("disabled")), (b.submenu || b.has_submenu) && l.classList.add("has_submenu"); - } - "function" == typeof b ? (l.dataset.value = a, l.onclick_callback = b) : l.dataset.value = b; - b.className && (l.className += " " + b.className); - } - this.root.appendChild(l); - g || l.addEventListener("click", e); - d.autoopen && l.addEventListener("mouseenter", f); - return l; - }; - E.prototype.close = function(a, b) { - this.root.parentNode && this.root.parentNode.removeChild(this.root); - this.parentMenu && !b && (this.parentMenu.lock = !1, this.parentMenu.current_submenu = null, void 0 === a ? this.parentMenu.close() : a && !E.isCursorOverElement(a, this.parentMenu.root) && E.trigger(this.parentMenu.root, "mouseleave", a)); - this.current_submenu && this.current_submenu.close(a, !0); - this.root.closing_timer && clearTimeout(this.root.closing_timer); - }; - E.trigger = function(a, b, d, f) { - var e = document.createEvent("CustomEvent"); - e.initCustomEvent(b, !0, !0, d); - e.srcElement = f; - a.dispatchEvent ? a.dispatchEvent(e) : a.__events && a.__events.dispatchEvent(e); - return e; - }; - E.prototype.getTopMenu = function() { - return this.options.parentMenu ? this.options.parentMenu.getTopMenu() : this; - }; - E.prototype.getFirstEvent = function() { - return this.options.parentMenu ? this.options.parentMenu.getFirstEvent() : this.options.event; - }; - E.isCursorOverElement = function(a, b) { - var d = a.clientX; - a = a.clientY; - return (b = b.getBoundingClientRect()) ? a > b.top && a < b.top + b.height && d > b.left && d < b.left + b.width ? !0 : !1 : !1; - }; - e.ContextMenu = E; - e.closeAllContextMenus = function(a) { - a = a || window; - a = a.document.querySelectorAll(".litecontextmenu"); - if (a.length) { - for (var b = [], d = 0; d < a.length; d++) { - b.push(a[d]); - } - for (d = 0; d < b.length; d++) { - b[d].close ? b[d].close() : b[d].parentNode && b[d].parentNode.removeChild(b[d]); - } - } - }; - e.extendClass = function(a, b) { - for (var d in b) { - a.hasOwnProperty(d) || (a[d] = b[d]); - } - if (b.prototype) { - for (d in b.prototype) { - b.prototype.hasOwnProperty(d) && !a.prototype.hasOwnProperty(d) && (b.prototype.__lookupGetter__(d) ? a.prototype.__defineGetter__(d, b.prototype.__lookupGetter__(d)) : a.prototype[d] = b.prototype[d], b.prototype.__lookupSetter__(d) && a.prototype.__defineSetter__(d, b.prototype.__lookupSetter__(d))); - } - } - }; - A.sampleCurve = function(a, b) { - if (b) { - for (var d = 0; d < b.length - 1; ++d) { - var f = b[d], e = b[d + 1]; - if (!(e[0] < a)) { - b = e[0] - f[0]; - if (0.00001 > Math.abs(b)) { - return f[1]; - } - a = (a - f[0]) / b; - return f[1] * (1.0 - a) + e[1] * a; - } - } - return 0; - } - }; - A.prototype.draw = function(a, b, d, f, e, c) { - if (d = this.points) { - this.size = b; - var l = b[0] - 2 * this.margin; - b = b[1] - 2 * this.margin; - e = e || "#666"; - a.save(); - a.translate(this.margin, this.margin); - f && (a.fillStyle = "#111", a.fillRect(0, 0, l, b), a.fillStyle = "#222", a.fillRect(0.5 * l, 0, 1, b), a.strokeStyle = "#333", a.strokeRect(0, 0, l, b)); - a.strokeStyle = e; - c && (a.globalAlpha = 0.5); - a.beginPath(); - for (f = 0; f < d.length; ++f) { - e = d[f], a.lineTo(e[0] * l, (1.0 - e[1]) * b); - } - a.stroke(); - a.globalAlpha = 1; - if (!c) { - for (f = 0; f < d.length; ++f) { - e = d[f], a.fillStyle = this.selected == f ? "#FFF" : this.nearest == f ? "#DDD" : "#AAA", a.beginPath(), a.arc(e[0] * l, (1.0 - e[1]) * b, 2, 0, 2 * Math.PI), a.fill(); - } - } - a.restore(); - } - }; - A.prototype.onMouseDown = function(a, b) { - var d = this.points; - if (d && !(0 > a[1])) { - var f = this.size[0] - 2 * this.margin, e = this.size[1] - 2 * this.margin, c = a[0] - this.margin; - a = a[1] - this.margin; - this.selected = this.getCloserPoint([c, a], 30 / b.ds.scale); - -1 == this.selected && (b = [c / f, 1 - a / e], d.push(b), d.sort(function(a, b) { - return a[0] - b[0]; - }), this.selected = d.indexOf(b), this.must_update = !0); - if (-1 != this.selected) { - return !0; - } - } - }; - A.prototype.onMouseMove = function(a, b) { - var d = this.points; - if (d) { - var f = this.selected; - if (!(0 > f)) { - var e = (a[0] - this.margin) / (this.size[0] - 2 * this.margin), c = (a[1] - this.margin) / (this.size[1] - 2 * this.margin); - this._nearest = this.getCloserPoint([a[0] - this.margin, a[1] - this.margin], 30 / b.ds.scale); - if (b = d[f]) { - var l = 0 == f || f == d.length - 1; - !l && (-10 > a[0] || a[0] > this.size[0] + 10 || -10 > a[1] || a[1] > this.size[1] + 10) ? (d.splice(f, 1), this.selected = -1) : (b[0] = l ? 0 == f ? 0 : 1 : Math.clamp(e, 0, 1), b[1] = 1.0 - Math.clamp(c, 0, 1), d.sort(function(a, b) { - return a[0] - b[0]; - }), this.selected = d.indexOf(b), this.must_update = !0); - } - } - } - }; - A.prototype.onMouseUp = function(a, b) { - this.selected = -1; - return !1; - }; - A.prototype.getCloserPoint = function(a, b) { - var d = this.points; - if (!d) { - return -1; - } - b = b || 30; - for (var f = this.size[0] - 2 * this.margin, e = this.size[1] - 2 * this.margin, c = d.length, l = [0, 0], g = 1000000, p = -1, m = 0; m < c; ++m) { - var h = d[m]; - l[0] = h[0] * f; - l[1] = (1.0 - h[1]) * e; - h = vec2.distance(a, l); - h > g || h > b || (p = m, g = h); - } - return p; - }; - e.CurveEditor = A; - e.getParameterNames = function(a) { - return (a + "").replace(/[/][/].*$/gm, "").replace(/\s+/g, "").replace(/[/][*][^/*]*[*][/]/g, "").split("){", 1)[0].replace(/^[^(]*[(]/, "").replace(/=[^,]+/g, "").split(",").filter(Boolean); - }; - Math.clamp = function(a, b, d) { - return b > a ? b : d < a ? d : a; - }; - "undefined" == typeof window || window.requestAnimationFrame || (window.requestAnimationFrame = window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function(a) { - window.setTimeout(a, 1000 / 60); - }); -})(this); -"undefined" != typeof exports && (exports.LiteGraph = this.LiteGraph); -(function(v) { - function c() { - this.addOutput("in ms", "number"); - this.addOutput("in sec", "number"); - } - function k() { - this.size = [140, 80]; - this.properties = {enabled:!0}; - this.enabled = !0; - this.subgraph = new r.LGraph; - this.subgraph._subgraph_node = this; - this.subgraph._is_subgraph = !0; - this.subgraph.onTrigger = this.onSubgraphTrigger.bind(this); - this.subgraph.onInputAdded = this.onSubgraphNewInput.bind(this); - this.subgraph.onInputRenamed = this.onSubgraphRenamedInput.bind(this); - this.subgraph.onInputTypeChanged = this.onSubgraphTypeChangeInput.bind(this); - this.subgraph.onInputRemoved = this.onSubgraphRemovedInput.bind(this); - this.subgraph.onOutputAdded = this.onSubgraphNewOutput.bind(this); - this.subgraph.onOutputRenamed = this.onSubgraphRenamedOutput.bind(this); - this.subgraph.onOutputTypeChanged = this.onSubgraphTypeChangeOutput.bind(this); - this.subgraph.onOutputRemoved = this.onSubgraphRemovedOutput.bind(this); - } - function h() { - this.addOutput("", "number"); - this.name_in_graph = ""; - this.properties = {name:"", type:"number", value:0}; - var f = this; - this.name_widget = this.addWidget("text", "Name", this.properties.name, function(a) { - a && f.setProperty("name", a); - }); - this.type_widget = this.addWidget("text", "Type", this.properties.type, function(a) { - f.setProperty("type", a); - }); - this.value_widget = this.addWidget("number", "Value", this.properties.value, function(a) { - f.setProperty("value", a); - }); - this.widgets_up = !0; - this.size = [180, 90]; - } - function m() { - this.addInput("", ""); - this.name_in_graph = ""; - this.properties = {}; - var f = this; - Object.defineProperty(this.properties, "name", {get:function() { - return f.name_in_graph; - }, set:function(a) { - "" != a && a != f.name_in_graph && (f.name_in_graph ? f.graph.renameOutput(f.name_in_graph, a) : f.graph.addOutput(a, f.properties.type), f.name_widget.value = a, f.name_in_graph = a); - }, enumerable:!0}); - Object.defineProperty(this.properties, "type", {get:function() { - return f.inputs[0].type; - }, set:function(a) { - if ("action" == a || "event" == a) { - a = r.ACTION; - } - f.inputs[0].type = a; - f.name_in_graph && f.graph.changeOutputType(f.name_in_graph, f.inputs[0].type); - f.type_widget.value = a || ""; - }, enumerable:!0}); - this.name_widget = this.addWidget("text", "Name", this.properties.name, function(a) { - a && (f.properties.name = a); - }); - this.type_widget = this.addWidget("text", "Type", this.properties.type, function(a) { - f.properties.type = a || ""; - }); - this.widgets_up = !0; - this.size = [180, 60]; - } - function t() { - this.addOutput("value", "number"); - this.addProperty("value", 1.0); - } - function g() { - this.addOutput("", "string"); - this.addProperty("value", ""); - this.widget = this.addWidget("text", "value", "", this.setValue.bind(this)); - this.widgets_up = !0; - this.size = [100, 30]; - } - function D() { - this.addOutput("", ""); - this.addProperty("value", ""); - this.widget = this.addWidget("text", "json", "", this.setValue.bind(this)); - this.widgets_up = !0; - this.size = [140, 30]; - this._value = null; - } - function B() { - this.addInput("obj", ""); - this.addOutput("", ""); - this.addProperty("value", ""); - this.widget = this.addWidget("text", "prop.", "", this.setValue.bind(this)); - this.widgets_up = !0; - this.size = [140, 30]; - this._value = null; - } - function x() { - this.addInput("obj", ""); - this.addOutput("keys", "array"); - this.size = [140, 30]; - } - function E() { - this.addInput("A", "object"); - this.addInput("B", "object"); - this.addOutput("", "object"); - this._result = {}; - var f = this; - this.addWidget("button", "clear", "", function() { - f._result = {}; - }); - this.size = this.computeSize(); - } - function A() { - this.size = [60, 30]; - this.addInput("in"); - this.addOutput("out"); - this.properties = {varname:"myname", global:!1}; - this.value = null; - } - function e() { - this.size = [60, 30]; - this.addInput("data", 0); - this.addInput("download", r.ACTION); - this.properties = {filename:"data.json"}; - this.value = null; - var f = this; - this.addWidget("button", "Download", "", function(a) { - f.value && f.downloadAsFile(); - }); - } - function y() { - this.size = [60, 30]; - this.addInput("value", 0, {label:""}); - this.value = 0; - } - function w() { - this.addInput("in", 0); - this.addOutput("out", 0); - this.size = [40, 30]; - } - function z() { - this.mode = r.ON_EVENT; - this.size = [80, 30]; - this.addProperty("msg", ""); - this.addInput("log", r.EVENT); - this.addInput("msg", 0); - } - function l() { - this.mode = r.ON_EVENT; - this.addProperty("msg", ""); - this.addInput("", r.EVENT); - var f = this; - this.widget = this.addWidget("text", "Text", "", function(a) { - f.properties.msg = a; - }); - this.widgets_up = !0; - this.size = [200, 30]; - } - function p() { - this.size = [60, 30]; - this.addProperty("onExecute", "return A;"); - this.addInput("A", ""); - this.addInput("B", ""); - this.addOutput("out", ""); - this._func = null; - this.data = {}; - } - var r = v.LiteGraph; - c.title = "Time"; - c.desc = "Time"; - c.prototype.onExecute = function() { - this.setOutputData(0, 1000 * this.graph.globaltime); - this.setOutputData(1, this.graph.globaltime); - }; - r.registerNodeType("basic/time", c); - k.title = "Subgraph"; - k.desc = "Graph inside a node"; - k.title_color = "#334"; - k.prototype.onGetInputs = function() { - return [["enabled", "boolean"]]; - }; - k.prototype.onDrawTitle = function(f) { - if (!this.flags.collapsed) { - f.fillStyle = "#555"; - var a = r.NODE_TITLE_HEIGHT, b = this.size[0] - a; - f.fillRect(b, -a, a, a); - f.fillStyle = "#333"; - f.beginPath(); - f.moveTo(b + 0.2 * a, 0.6 * -a); - f.lineTo(b + 0.8 * a, 0.6 * -a); - f.lineTo(b + 0.5 * a, 0.3 * -a); - f.fill(); - } - }; - k.prototype.onDblClick = function(f, a, b) { - var d = this; - setTimeout(function() { - b.openSubgraph(d.subgraph); - }, 10); - }; - k.prototype.onMouseDown = function(f, a, b) { - if (!this.flags.collapsed && a[0] > this.size[0] - r.NODE_TITLE_HEIGHT && 0 > a[1]) { - var d = this; - setTimeout(function() { - b.openSubgraph(d.subgraph); - }, 10); - } - }; - k.prototype.onAction = function(f, a) { - this.subgraph.onAction(f, a); - }; - k.prototype.onExecute = function() { - if (this.enabled = this.getInputOrProperty("enabled")) { - if (this.inputs) { - for (var f = 0; f < this.inputs.length; f++) { - var a = this.inputs[f], b = this.getInputData(f); - this.subgraph.setInputData(a.name, b); - } - } - this.subgraph.runStep(); - if (this.outputs) { - for (f = 0; f < this.outputs.length; f++) { - b = this.subgraph.getOutputData(this.outputs[f].name), this.setOutputData(f, b); - } - } - } - }; - k.prototype.sendEventToAllNodes = function(f, a, b) { - this.enabled && this.subgraph.sendEventToAllNodes(f, a, b); - }; - k.prototype.onSubgraphTrigger = function(f, a) { - f = this.findOutputSlot(f); - -1 != f && this.triggerSlot(f); - }; - k.prototype.onSubgraphNewInput = function(f, a) { - -1 == this.findInputSlot(f) && this.addInput(f, a); - }; - k.prototype.onSubgraphRenamedInput = function(f, a) { - f = this.findInputSlot(f); - -1 != f && (this.getInputInfo(f).name = a); - }; - k.prototype.onSubgraphTypeChangeInput = function(f, a) { - f = this.findInputSlot(f); - -1 != f && (this.getInputInfo(f).type = a); - }; - k.prototype.onSubgraphRemovedInput = function(f) { - f = this.findInputSlot(f); - -1 != f && this.removeInput(f); - }; - k.prototype.onSubgraphNewOutput = function(f, a) { - -1 == this.findOutputSlot(f) && this.addOutput(f, a); - }; - k.prototype.onSubgraphRenamedOutput = function(f, a) { - f = this.findOutputSlot(f); - -1 != f && (this.getOutputInfo(f).name = a); - }; - k.prototype.onSubgraphTypeChangeOutput = function(f, a) { - f = this.findOutputSlot(f); - -1 != f && (this.getOutputInfo(f).type = a); - }; - k.prototype.onSubgraphRemovedOutput = function(f) { - f = this.findInputSlot(f); - -1 != f && this.removeOutput(f); - }; - k.prototype.getExtraMenuOptions = function(f) { - var a = this; - return [{content:"Open", callback:function() { - f.openSubgraph(a.subgraph); - }}]; - }; - k.prototype.onResize = function(f) { - f[1] += 20; - }; - k.prototype.serialize = function() { - var f = LGraphNode.prototype.serialize.call(this); - f.subgraph = this.subgraph.serialize(); - return f; - }; - k.prototype.clone = function() { - var f = r.createNode(this.type), a = this.serialize(); - delete a.id; - delete a.inputs; - delete a.outputs; - f.configure(a); - return f; - }; - r.Subgraph = k; - r.registerNodeType("graph/subgraph", k); - h.title = "Input"; - h.desc = "Input of the graph"; - h.prototype.onConfigure = function() { - this.updateType(); - }; - h.prototype.updateType = function() { - var f = this.properties.type; - this.type_widget.value = f; - "number" == f ? (this.value_widget.type = "number", this.value_widget.value = 0) : "bool" == f ? (this.value_widget.type = "toggle", this.value_widget.value = !0) : "string" == f ? (this.value_widget.type = "text", this.value_widget.value = "") : (this.value_widget.type = null, this.value_widget.value = null); - this.properties.value = this.value_widget.value; - }; - h.prototype.onPropertyChanged = function(f, a) { - if ("name" == f) { - if ("" == a || a == this.name_in_graph || "enabled" == a) { - return !1; - } - this.graph && (this.name_in_graph ? this.graph.renameInput(this.name_in_graph, a) : this.graph.addInput(a, this.properties.type)); - this.name_in_graph = this.name_widget.value = a; - } else { - "type" == f && this.updateType(a || ""); - } - }; - h.prototype.getTitle = function() { - return this.flags.collapsed ? this.properties.name : this.title; - }; - h.prototype.onAction = function(f, a) { - this.properties.type == r.EVENT && this.triggerSlot(0, a); - }; - h.prototype.onExecute = function() { - var f = this.graph.inputs[this.properties.name]; - f || this.setOutputData(0, this.properties.value); - this.setOutputData(0, void 0 === f.value ? this.properties.value : f.value); - }; - h.prototype.onRemoved = function() { - this.name_in_graph && this.graph.removeInput(this.name_in_graph); - }; - r.GraphInput = h; - r.registerNodeType("graph/input", h); - m.title = "Output"; - m.desc = "Output of the graph"; - m.prototype.onExecute = function() { - this._value = this.getInputData(0); - this.graph.setOutputData(this.properties.name, this._value); - }; - m.prototype.onAction = function(f, a) { - this.properties.type == r.ACTION && this.graph.trigger(this.properties.name, a); - }; - m.prototype.onRemoved = function() { - this.name_in_graph && this.graph.removeOutput(this.name_in_graph); - }; - m.prototype.getTitle = function() { - return this.flags.collapsed ? this.properties.name : this.title; - }; - r.GraphOutput = m; - r.registerNodeType("graph/output", m); - t.title = "Const Number"; - t.desc = "Constant number"; - t.prototype.onExecute = function() { - this.setOutputData(0, parseFloat(this.properties.value)); - }; - t.prototype.getTitle = function() { - return this.flags.collapsed ? this.properties.value : this.title; - }; - t.prototype.setValue = function(f) { - this.properties.value = f; - }; - t.prototype.onDrawBackground = function(f) { - this.outputs[0].label = this.properties.value.toFixed(3); - }; - r.registerNodeType("basic/const", t); - g.title = "Const String"; - g.desc = "Constant string"; - g.prototype.setValue = function(f) { - this.properties.value = f; - }; - g.prototype.onPropertyChanged = function(f, a) { - this.widget.value = a; - }; - g.prototype.getTitle = t.prototype.getTitle; - g.prototype.onExecute = function() { - this.setOutputData(0, this.properties.value); - }; - r.registerNodeType("basic/string", g); - D.title = "Const Data"; - D.desc = "Constant Data"; - D.prototype.setValue = function(f) { - this.properties.value = f; - this.onPropertyChanged("value", f); - }; - D.prototype.onPropertyChanged = function(f, a) { - this.widget.value = a; - if (null != a && "" != a) { - try { - this._value = JSON.parse(a), this.boxcolor = "#AEA"; - } catch (b) { - this.boxcolor = "red"; - } - } - }; - D.prototype.onExecute = function() { - this.setOutputData(0, this._value); - }; - r.registerNodeType("basic/data", D); - B.title = "Object property"; - B.desc = "Outputs the property of an object"; - B.prototype.setValue = function(f) { - this.properties.value = f; - this.widget.value = f; - }; - B.prototype.getTitle = function() { - return this.flags.collapsed ? "in." + this.properties.value : this.title; - }; - B.prototype.onPropertyChanged = function(f, a) { - this.widget.value = a; - }; - B.prototype.onExecute = function() { - var f = this.getInputData(0); - null != f && this.setOutputData(0, f[this.properties.value]); - }; - r.registerNodeType("basic/object_property", B); - x.title = "Object keys"; - x.desc = "Outputs an array with the keys of an object"; - x.prototype.onExecute = function() { - var f = this.getInputData(0); - null != f && this.setOutputData(0, Object.keys(f)); - }; - r.registerNodeType("basic/object_keys", x); - E.title = "Merge Objects"; - E.desc = "Creates an object copying properties from others"; - E.prototype.onExecute = function() { - var f = this.getInputData(0), a = this.getInputData(1), b = this._result; - if (f) { - for (var d in f) { - b[d] = f[d]; - } - } - if (a) { - for (d in a) { - b[d] = a[d]; - } - } - this.setOutputData(0, b); - }; - r.registerNodeType("basic/merge_objects", E); - A.title = "Variable"; - A.desc = "store/read variable value"; - A.prototype.onExecute = function() { - this.value = this.getInputData(0); - this.graph && (this.graph.vars[this.properties.varname] = this.value); - this.properties.global && (v[this.properties.varname] = this.value); - this.setOutputData(0, this.value); - }; - A.prototype.getTitle = function() { - return this.properties.varname; - }; - r.registerNodeType("basic/variable", A); - e.title = "Download"; - e.desc = "Download some data"; - e.prototype.downloadAsFile = function() { - if (null != this.value) { - var f = null; - f = this.value.constructor === String ? this.value : JSON.stringify(this.value); - f = new Blob([f]); - var a = URL.createObjectURL(f); - f = document.createElement("a"); - f.setAttribute("href", a); - f.setAttribute("download", this.properties.filename); - f.style.display = "none"; - document.body.appendChild(f); - f.click(); - document.body.removeChild(f); - setTimeout(function() { - URL.revokeObjectURL(a); - }, 6E4); - } - }; - e.prototype.onAction = function(f, a) { - var b = this; - setTimeout(function() { - b.downloadAsFile(); - }, 100); - }; - e.prototype.onExecute = function() { - this.inputs[0] && (this.value = this.getInputData(0)); - }; - e.prototype.getTitle = function() { - return this.flags.collapsed ? this.properties.filename : this.title; - }; - r.registerNodeType("basic/download", e); - y.title = "Watch"; - y.desc = "Show value of input"; - y.prototype.onExecute = function() { - this.inputs[0] && (this.value = this.getInputData(0)); - }; - y.prototype.getTitle = function() { - return this.flags.collapsed ? this.inputs[0].label : this.title; - }; - y.toString = function(f) { - if (null == f) { - return "null"; - } - if (f.constructor === Number) { - return f.toFixed(3); - } - if (f.constructor === Array) { - for (var a = "[", b = 0; b < f.length; ++b) { - a += y.toString(f[b]) + (b + 1 != f.length ? "," : ""); - } - return a + "]"; - } - return String(f); - }; - y.prototype.onDrawBackground = function(f) { - this.inputs[0].label = y.toString(this.value); - }; - r.registerNodeType("basic/watch", y); - w.title = "Cast"; - w.desc = "Allows to connect different types"; - w.prototype.onExecute = function() { - this.setOutputData(0, this.getInputData(0)); - }; - r.registerNodeType("basic/cast", w); - z.title = "Console"; - z.desc = "Show value inside the console"; - z.prototype.onAction = function(f, a) { - "log" == f ? console.log(a) : "warn" == f ? console.warn(a) : "error" == f && console.error(a); - }; - z.prototype.onExecute = function() { - var f = this.getInputData(1); - null !== f && (this.properties.msg = f); - console.log(f); - }; - z.prototype.onGetInputs = function() { - return [["log", r.ACTION], ["warn", r.ACTION], ["error", r.ACTION]]; - }; - r.registerNodeType("basic/console", z); - l.title = "Alert"; - l.desc = "Show an alert window"; - l.color = "#510"; - l.prototype.onConfigure = function(f) { - this.widget.value = f.properties.msg; - }; - l.prototype.onAction = function(f, a) { - var b = this.properties.msg; - setTimeout(function() { - alert(b); - }, 10); - }; - r.registerNodeType("basic/alert", l); - p.prototype.onConfigure = function(f) { - f.properties.onExecute && r.allow_scripts ? this.compileCode(f.properties.onExecute) : console.warn("Script not compiled, LiteGraph.allow_scripts is false"); - }; - p.title = "Script"; - p.desc = "executes a code (max 100 characters)"; - p.widgets_info = {onExecute:{type:"code"}}; - p.prototype.onPropertyChanged = function(f, a) { - "onExecute" == f && r.allow_scripts ? this.compileCode(a) : console.warn("Script not compiled, LiteGraph.allow_scripts is false"); - }; - p.prototype.compileCode = function(f) { - this._func = null; - if (256 < f.length) { - console.warn("Script too long, max 256 chars"); - } else { - for (var a = f.toLowerCase(), b = "script body document eval nodescript function".split(" "), d = 0; d < b.length; ++d) { - if (-1 != a.indexOf(b[d])) { - console.warn("invalid script"); - return; - } - } - try { - this._func = new Function("A", "B", "C", "DATA", "node", f); - } catch (q) { - console.error("Error parsing script"), console.error(q); - } - } - }; - p.prototype.onExecute = function() { - if (this._func) { - try { - var f = this.getInputData(0), a = this.getInputData(1), b = this.getInputData(2); - this.setOutputData(0, this._func(f, a, b, this.data, this)); - } catch (d) { - console.error("Error in script"), console.error(d); - } - } - }; - p.prototype.onGetOutputs = function() { - return [["C", ""]]; - }; - r.registerNodeType("basic/script", p); -})(this); -(function(v) { - function c() { - this.size = [60, 30]; - this.addInput("event", x.ACTION); - } - function k() { - this.size = [60, 30]; - this.addInput("in", ""); - this.addOutput("true", x.EVENT); - this.addOutput("change", x.EVENT); - this.was_true = !1; - } - function h() { - this.addInput("", x.ACTION); - this.addInput("", x.ACTION); - this.addInput("", x.ACTION); - this.addInput("", x.ACTION); - this.addInput("", x.ACTION); - this.addInput("", x.ACTION); - this.addOutput("", x.EVENT); - this.addOutput("", x.EVENT); - this.addOutput("", x.EVENT); - this.addOutput("", x.EVENT); - this.addOutput("", x.EVENT); - this.addOutput("", x.EVENT); - this.size = [120, 30]; - this.flags = {horizontal:!0, render_box:!1}; - } - function m() { - this.size = [60, 30]; - this.addInput("event", x.ACTION); - this.addOutput("event", x.EVENT); - this.properties = {equal_to:"", has_property:"", property_equal_to:""}; - } - function t() { - this.addInput("inc", x.ACTION); - this.addInput("dec", x.ACTION); - this.addInput("reset", x.ACTION); - this.addOutput("change", x.EVENT); - this.addOutput("num", "number"); - this.num = 0; - } - function g() { - this.size = [60, 30]; - this.addProperty("time_in_ms", 1000); - this.addInput("event", x.ACTION); - this.addOutput("on_time", x.EVENT); - this._pending = []; - } - function D() { - this.addProperty("interval", 1000); - this.addProperty("event", "tick"); - this.addOutput("on_tick", x.EVENT); - this.time = 0; - this.last_interval = 1000; - this.triggered = !1; - } - function B() { - this.addInput("data", ""); - this.addInput("assign", x.ACTION); - this.addOutput("data", ""); - this._last_value = null; - this.properties = {data:null, serialize:!0}; - var c = this; - this.addWidget("button", "store", "", function() { - c.properties.data = c._last_value; - }); - } - var x = v.LiteGraph; - c.title = "Log Event"; - c.desc = "Log event in console"; - c.prototype.onAction = function(c, g) { - console.log(c, g); - }; - x.registerNodeType("events/log", c); - k.title = "TriggerEvent"; - k.desc = "Triggers event if value is true"; - k.prototype.onExecute = function(c, g) { - (c = this.getInputData(0)) && this.triggerSlot(0, g); - c && !this.was_true && this.triggerSlot(1, g); - this.was_true = c; - }; - x.registerNodeType("events/trigger", k); - h.title = "Sequencer"; - h.desc = "Trigger events when an event arrives"; - h.prototype.getTitle = function() { - return ""; - }; - h.prototype.onAction = function(c, g) { - if (this.outputs) { - for (c = 0; c < this.outputs.length; ++c) { - this.triggerSlot(c, g); - } - } - }; - x.registerNodeType("events/sequencer", h); - m.title = "Filter Event"; - m.desc = "Blocks events that do not match the filter"; - m.prototype.onAction = function(c, g) { - if (null != g && (!this.properties.equal_to || this.properties.equal_to == g)) { - if (this.properties.has_property && (c = g[this.properties.has_property], null == c || this.properties.property_equal_to && this.properties.property_equal_to != c)) { - return; - } - this.triggerSlot(0, g); - } - }; - x.registerNodeType("events/filter", m); - t.title = "Counter"; - t.desc = "Counts events"; - t.prototype.getTitle = function() { - return this.flags.collapsed ? String(this.num) : this.title; - }; - t.prototype.onAction = function(c, g) { - g = this.num; - "inc" == c ? this.num += 1 : "dec" == c ? --this.num : "reset" == c && (this.num = 0); - this.num != g && this.trigger("change", this.num); - }; - t.prototype.onDrawBackground = function(c) { - this.flags.collapsed || (c.fillStyle = "#AAA", c.font = "20px Arial", c.textAlign = "center", c.fillText(this.num, 0.5 * this.size[0], 0.5 * this.size[1])); - }; - t.prototype.onExecute = function() { - this.setOutputData(1, this.num); - }; - x.registerNodeType("events/counter", t); - g.title = "Delay"; - g.desc = "Delays one event"; - g.prototype.onAction = function(c, g) { - c = this.properties.time_in_ms; - 0 >= c ? this.trigger(null, g) : this._pending.push([c, g]); - }; - g.prototype.onExecute = function() { - var c = 1000 * this.graph.elapsed_time; - this.isInputConnected(1) && (this.properties.time_in_ms = this.getInputData(1)); - for (var g = 0; g < this._pending.length; ++g) { - var e = this._pending[g]; - e[0] -= c; - 0 < e[0] || (this._pending.splice(g, 1), --g, this.trigger(null, e[1])); - } - }; - g.prototype.onGetInputs = function() { - return [["event", x.ACTION], ["time_in_ms", "number"]]; - }; - x.registerNodeType("events/delay", g); - D.title = "Timer"; - D.desc = "Sends an event every N milliseconds"; - D.prototype.onStart = function() { - this.time = 0; - }; - D.prototype.getTitle = function() { - return "Timer: " + this.last_interval.toString() + "ms"; - }; - D.on_color = "#AAA"; - D.off_color = "#222"; - D.prototype.onDrawBackground = function() { - this.boxcolor = this.triggered ? D.on_color : D.off_color; - this.triggered = !1; - }; - D.prototype.onExecute = function() { - var c = 0 == this.time; - this.time += 1000 * this.graph.elapsed_time; - this.last_interval = Math.max(1, this.getInputOrProperty("interval") | 0); - !c && (this.time < this.last_interval || isNaN(this.last_interval)) ? this.inputs && 1 < this.inputs.length && this.inputs[1] && this.setOutputData(1, !1) : (this.triggered = !0, this.time %= this.last_interval, this.trigger("on_tick", this.properties.event), this.inputs && 1 < this.inputs.length && this.inputs[1] && this.setOutputData(1, !0)); - }; - D.prototype.onGetInputs = function() { - return [["interval", "number"]]; - }; - D.prototype.onGetOutputs = function() { - return [["tick", "boolean"]]; - }; - x.registerNodeType("events/timer", D); - B.title = "Data Store"; - B.desc = "Stores data and only changes when event is received"; - B.prototype.onExecute = function() { - this._last_value = this.getInputData(0); - this.setOutputData(0, this.properties.data); - }; - B.prototype.onAction = function(c, g) { - this.properties.data = this._last_value; - }; - B.prototype.onSerialize = function(c) { - null != c.data && (0 == this.properties.serialize || c.data.constructor !== String && c.data.constructor !== Number && c.data.constructor !== Boolean && c.data.constructor !== Array && c.data.constructor !== Object) && (c.data = null); - }; - x.registerNodeType("basic/data_store", B); -})(this); -(function(v) { - function c() { - this.addOutput("", A.EVENT); - this.addOutput("", "boolean"); - this.addProperty("text", "click me"); - this.addProperty("font_size", 30); - this.addProperty("message", ""); - this.size = [164, 84]; - this.clicked = !1; - } - function k() { - this.addInput("", "boolean"); - this.addInput("e", A.ACTION); - this.addOutput("v", "boolean"); - this.addOutput("e", A.EVENT); - this.properties = {font:"", value:!1}; - this.size = [160, 44]; - } - function h() { - this.addOutput("", "number"); - this.size = [80, 60]; - this.properties = {min:-1000, max:1000, value:1, step:1}; - this.old_y = -1; - this._precision = this._remainder = 0; - this.mouse_captured = !1; - } - function m() { - this.addOutput("", "string"); - this.addOutput("change", A.EVENT); - this.size = [80, 60]; - this.properties = {value:"A", values:"A;B;C"}; - this.old_y = -1; - this.mouse_captured = !1; - this._values = this.properties.values.split(";"); - var c = this; - this.widgets_up = !0; - this.widget = this.addWidget("combo", "", this.properties.value, function(e) { - c.properties.value = e; - c.triggerSlot(1, e); - }, {property:"value", values:this._values}); - } - function t() { - this.addOutput("", "number"); - this.size = [64, 84]; - this.properties = {min:0, max:1, value:0.5, color:"#7AF", precision:2}; - this.value = -1; - } - function g() { - this.addOutput("", "number"); - this.properties = {value:0.5, min:0, max:1, text:"V"}; - var c = this; - this.size = [140, 40]; - this.slider = this.addWidget("slider", "V", this.properties.value, function(e) { - c.properties.value = e; - }, this.properties); - this.widgets_up = !0; - } - function D() { - this.size = [160, 26]; - this.addOutput("", "number"); - this.properties = {color:"#7AF", min:0, max:1, value:0.5}; - this.value = -1; - } - function B() { - this.size = [160, 26]; - this.addInput("", "number"); - this.properties = {min:0, max:1, value:0, color:"#AAF"}; - } - function x() { - this.addInputs("", 0); - this.properties = {value:"...", font:"Arial", fontsize:18, color:"#AAA", align:"left", glowSize:0, decimals:1}; - } - function E() { - this.size = [200, 100]; - this.properties = {borderColor:"#ffffff", bgcolorTop:"#f0f0f0", bgcolorBottom:"#e0e0e0", shadowSize:2, borderRadius:3}; - } - var A = v.LiteGraph; - c.title = "Button"; - c.desc = "Triggers an event"; - c.font = "Arial"; - c.prototype.onDrawForeground = function(e) { - if (!this.flags.collapsed && (e.fillStyle = "black", e.fillRect(11, 11, this.size[0] - 20, this.size[1] - 20), e.fillStyle = "#AAF", e.fillRect(9, 9, this.size[0] - 20, this.size[1] - 20), e.fillStyle = this.clicked ? "white" : this.mouseOver ? "#668" : "#334", e.fillRect(10, 10, this.size[0] - 20, this.size[1] - 20), this.properties.text || 0 === this.properties.text)) { - var g = this.properties.font_size || 30; - e.textAlign = "center"; - e.fillStyle = this.clicked ? "black" : "white"; - e.font = g + "px " + c.font; - e.fillText(this.properties.text, 0.5 * this.size[0], 0.5 * this.size[1] + 0.3 * g); - e.textAlign = "left"; - } - }; - c.prototype.onMouseDown = function(c, g) { - if (1 < g[0] && 1 < g[1] && g[0] < this.size[0] - 2 && g[1] < this.size[1] - 2) { - return this.clicked = !0, this.triggerSlot(0, this.properties.message), !0; - } - }; - c.prototype.onExecute = function() { - this.setOutputData(1, this.clicked); - }; - c.prototype.onMouseUp = function(c) { - this.clicked = !1; - }; - A.registerNodeType("widget/button", c); - k.title = "Toggle"; - k.desc = "Toggles between true or false"; - k.prototype.onDrawForeground = function(c) { - if (!this.flags.collapsed) { - var e = 0.5 * this.size[1], g = 0.8 * this.size[1]; - c.font = this.properties.font || (0.8 * e).toFixed(0) + "px Arial"; - var m = c.measureText(this.title).width; - m = 0.5 * (this.size[0] - (m + e)); - c.fillStyle = "#AAA"; - c.fillRect(m, g - e, e, e); - c.fillStyle = this.properties.value ? "#AEF" : "#000"; - c.fillRect(m + 0.25 * e, g - e + 0.25 * e, .5 * e, .5 * e); - c.textAlign = "left"; - c.fillStyle = "#AAA"; - c.fillText(this.title, 1.2 * e + m, 0.85 * g); - c.textAlign = "left"; - } - }; - k.prototype.onAction = function(c) { - this.properties.value = !this.properties.value; - this.trigger("e", this.properties.value); - }; - k.prototype.onExecute = function() { - var c = this.getInputData(0); - null != c && (this.properties.value = c); - this.setOutputData(0, this.properties.value); - }; - k.prototype.onMouseDown = function(c, g) { - if (1 < g[0] && 1 < g[1] && g[0] < this.size[0] - 2 && g[1] < this.size[1] - 2) { - return this.properties.value = !this.properties.value, this.graph._version++, this.trigger("e", this.properties.value), !0; - } - }; - A.registerNodeType("widget/toggle", k); - h.title = "Number"; - h.desc = "Widget to select number value"; - h.pixels_threshold = 10; - h.markers_color = "#666"; - h.prototype.onDrawForeground = function(c) { - var e = 0.5 * this.size[0], g = this.size[1]; - 30 < g ? (c.fillStyle = h.markers_color, c.beginPath(), c.moveTo(e, 0.1 * g), c.lineTo(e + 0.1 * g, 0.2 * g), c.lineTo(e + -0.1 * g, 0.2 * g), c.fill(), c.beginPath(), c.moveTo(e, 0.9 * g), c.lineTo(e + 0.1 * g, 0.8 * g), c.lineTo(e + -0.1 * g, 0.8 * g), c.fill(), c.font = (0.7 * g).toFixed(1) + "px Arial") : c.font = (0.8 * g).toFixed(1) + "px Arial"; - c.textAlign = "center"; - c.font = (0.7 * g).toFixed(1) + "px Arial"; - c.fillStyle = "#EEE"; - c.fillText(this.properties.value.toFixed(this._precision), e, 0.75 * g); - }; - h.prototype.onExecute = function() { - this.setOutputData(0, this.properties.value); - }; - h.prototype.onPropertyChanged = function(c, g) { - c = (this.properties.step + "").split("."); - this._precision = 1 < c.length ? c[1].length : 0; - }; - h.prototype.onMouseDown = function(c, g) { - if (!(0 > g[1])) { - return this.old_y = c.canvasY, this.captureInput(!0), this.mouse_captured = !0; - } - }; - h.prototype.onMouseMove = function(c) { - if (this.mouse_captured) { - var e = this.old_y - c.canvasY; - c.shiftKey && (e *= 10); - if (c.metaKey || c.altKey) { - e *= 0.1; - } - this.old_y = c.canvasY; - c = this._remainder + e / h.pixels_threshold; - this._remainder = c % 1; - c = Math.clamp(this.properties.value + (c | 0) * this.properties.step, this.properties.min, this.properties.max); - this.properties.value = c; - this.graph._version++; - this.setDirtyCanvas(!0); - } - }; - h.prototype.onMouseUp = function(c, g) { - 200 > c.click_time && (this.properties.value = Math.clamp(this.properties.value + (g[1] > 0.5 * this.size[1] ? -1 : 1) * this.properties.step, this.properties.min, this.properties.max), this.graph._version++, this.setDirtyCanvas(!0)); - this.mouse_captured && (this.mouse_captured = !1, this.captureInput(!1)); - }; - A.registerNodeType("widget/number", h); - m.title = "Combo"; - m.desc = "Widget to select from a list"; - m.prototype.onExecute = function() { - this.setOutputData(0, this.properties.value); - }; - m.prototype.onPropertyChanged = function(c, g) { - "values" == c ? (this._values = g.split(";"), this.widget.options.values = this._values) : "value" == c && (this.widget.value = g); - }; - A.registerNodeType("widget/combo", m); - t.title = "Knob"; - t.desc = "Circular controller"; - t.size = [80, 100]; - t.prototype.onDrawForeground = function(c) { - if (!this.flags.collapsed) { - -1 == this.value && (this.value = (this.properties.value - this.properties.min) / (this.properties.max - this.properties.min)); - var e = 0.5 * this.size[0], g = 0.5 * this.size[1], m = 0.5 * Math.min(this.size[0], this.size[1]) - 5; - c.globalAlpha = 1; - c.save(); - c.translate(e, g); - c.rotate(0.75 * Math.PI); - c.fillStyle = "rgba(0,0,0,0.5)"; - c.beginPath(); - c.moveTo(0, 0); - c.arc(0, 0, m, 0, 1.5 * Math.PI); - c.fill(); - c.strokeStyle = "black"; - c.fillStyle = this.properties.color; - c.lineWidth = 2; - c.beginPath(); - c.moveTo(0, 0); - c.arc(0, 0, m - 4, 0, 1.5 * Math.PI * Math.max(0.01, this.value)); - c.closePath(); - c.fill(); - c.lineWidth = 1; - c.globalAlpha = 1; - c.restore(); - c.fillStyle = "black"; - c.beginPath(); - c.arc(e, g, 0.75 * m, 0, 2 * Math.PI, !0); - c.fill(); - c.fillStyle = this.mouseOver ? "white" : this.properties.color; - c.beginPath(); - var l = this.value * Math.PI * 1.5 + 0.75 * Math.PI; - c.arc(e + Math.cos(l) * m * 0.65, g + Math.sin(l) * m * 0.65, 0.05 * m, 0, 2 * Math.PI, !0); - c.fill(); - c.fillStyle = this.mouseOver ? "white" : "#AAA"; - c.font = Math.floor(0.5 * m) + "px Arial"; - c.textAlign = "center"; - c.fillText(this.properties.value.toFixed(this.properties.precision), e, g + 0.15 * m); - } - }; - t.prototype.onExecute = function() { - this.setOutputData(0, this.properties.value); - this.boxcolor = A.colorToString([this.value, this.value, this.value]); - }; - t.prototype.onMouseDown = function(c) { - this.center = [0.5 * this.size[0], 0.5 * this.size[1] + 20]; - this.radius = 0.5 * this.size[0]; - if (20 > c.canvasY - this.pos[1] || A.distance([c.canvasX, c.canvasY], [this.pos[0] + this.center[0], this.pos[1] + this.center[1]]) > this.radius) { - return !1; - } - this.oldmouse = [c.canvasX - this.pos[0], c.canvasY - this.pos[1]]; - this.captureInput(!0); - return !0; - }; - t.prototype.onMouseMove = function(c) { - if (this.oldmouse) { - c = [c.canvasX - this.pos[0], c.canvasY - this.pos[1]]; - var e = this.value; - e -= 0.01 * (c[1] - this.oldmouse[1]); - 1.0 < e ? e = 1.0 : 0.0 > e && (e = 0.0); - this.value = e; - this.properties.value = this.properties.min + (this.properties.max - this.properties.min) * this.value; - this.oldmouse = c; - this.setDirtyCanvas(!0); - } - }; - t.prototype.onMouseUp = function(c) { - this.oldmouse && (this.oldmouse = null, this.captureInput(!1)); - }; - t.prototype.onPropertyChanged = function(c, g) { - if ("min" == c || "max" == c || "value" == c) { - return this.properties[c] = parseFloat(g), !0; - } - }; - A.registerNodeType("widget/knob", t); - g.title = "Inner Slider"; - g.prototype.onPropertyChanged = function(c, g) { - "value" == c && (this.slider.value = g); - }; - g.prototype.onExecute = function() { - this.setOutputData(0, this.properties.value); - }; - A.registerNodeType("widget/internal_slider", g); - D.title = "H.Slider"; - D.desc = "Linear slider controller"; - D.prototype.onDrawForeground = function(c) { - -1 == this.value && (this.value = (this.properties.value - this.properties.min) / (this.properties.max - this.properties.min)); - c.globalAlpha = 1; - c.lineWidth = 1; - c.fillStyle = "#000"; - c.fillRect(2, 2, this.size[0] - 4, this.size[1] - 4); - c.fillStyle = this.properties.color; - c.beginPath(); - c.rect(4, 4, (this.size[0] - 8) * this.value, this.size[1] - 8); - c.fill(); - }; - D.prototype.onExecute = function() { - this.properties.value = this.properties.min + (this.properties.max - this.properties.min) * this.value; - this.setOutputData(0, this.properties.value); - this.boxcolor = A.colorToString([this.value, this.value, this.value]); - }; - D.prototype.onMouseDown = function(c) { - if (0 > c.canvasY - this.pos[1]) { - return !1; - } - this.oldmouse = [c.canvasX - this.pos[0], c.canvasY - this.pos[1]]; - this.captureInput(!0); - return !0; - }; - D.prototype.onMouseMove = function(c) { - if (this.oldmouse) { - c = [c.canvasX - this.pos[0], c.canvasY - this.pos[1]]; - var e = this.value; - e += (c[0] - this.oldmouse[0]) / this.size[0]; - 1.0 < e ? e = 1.0 : 0.0 > e && (e = 0.0); - this.value = e; - this.oldmouse = c; - this.setDirtyCanvas(!0); - } - }; - D.prototype.onMouseUp = function(c) { - this.oldmouse = null; - this.captureInput(!1); - }; - D.prototype.onMouseLeave = function(c) { - }; - A.registerNodeType("widget/hslider", D); - B.title = "Progress"; - B.desc = "Shows data in linear progress"; - B.prototype.onExecute = function() { - var c = this.getInputData(0); - void 0 != c && (this.properties.value = c); - }; - B.prototype.onDrawForeground = function(c) { - c.lineWidth = 1; - c.fillStyle = this.properties.color; - var e = (this.properties.value - this.properties.min) / (this.properties.max - this.properties.min); - e = Math.min(1, e); - e = Math.max(0, e); - c.fillRect(2, 2, (this.size[0] - 4) * e, this.size[1] - 4); - }; - A.registerNodeType("widget/progress", B); - x.title = "Text"; - x.desc = "Shows the input value"; - x.widgets = [{name:"resize", text:"Resize box", type:"button"}, {name:"led_text", text:"LED", type:"minibutton"}, {name:"normal_text", text:"Normal", type:"minibutton"}]; - x.prototype.onDrawForeground = function(c) { - c.fillStyle = this.properties.color; - var e = this.properties.value; - this.properties.glowSize ? (c.shadowColor = this.properties.color, c.shadowOffsetX = 0, c.shadowOffsetY = 0, c.shadowBlur = this.properties.glowSize) : c.shadowColor = "transparent"; - var g = this.properties.fontsize; - c.textAlign = this.properties.align; - c.font = g.toString() + "px " + this.properties.font; - this.str = "number" == typeof e ? e.toFixed(this.properties.decimals) : e; - if ("string" == typeof this.str) { - e = this.str.split("\\n"); - for (var m = 0; m < e.length; m++) { - c.fillText(e[m], "left" == this.properties.align ? 15 : this.size[0] - 15, -0.15 * g + g * (parseInt(m) + 1)); - } - } - c.shadowColor = "transparent"; - this.last_ctx = c; - c.textAlign = "left"; - }; - x.prototype.onExecute = function() { - var c = this.getInputData(0); - null != c && (this.properties.value = c); - }; - x.prototype.resize = function() { - if (this.last_ctx) { - var c = this.str.split("\\n"); - this.last_ctx.font = this.properties.fontsize + "px " + this.properties.font; - for (var g = 0, m = 0; m < c.length; m++) { - var h = this.last_ctx.measureText(c[m]).width; - g < h && (g = h); - } - this.size[0] = g + 20; - this.size[1] = 4 + c.length * this.properties.fontsize; - this.setDirtyCanvas(!0); - } - }; - x.prototype.onPropertyChanged = function(c, g) { - this.properties[c] = g; - this.str = "number" == typeof g ? g.toFixed(3) : g; - return !0; - }; - A.registerNodeType("widget/text", x); - E.title = "Panel"; - E.desc = "Non interactive panel"; - E.widgets = [{name:"update", text:"Update", type:"button"}]; - E.prototype.createGradient = function(c) { - "" == this.properties.bgcolorTop || "" == this.properties.bgcolorBottom ? this.lineargradient = 0 : (this.lineargradient = c.createLinearGradient(0, 0, 0, this.size[1]), this.lineargradient.addColorStop(0, this.properties.bgcolorTop), this.lineargradient.addColorStop(1, this.properties.bgcolorBottom)); - }; - E.prototype.onDrawForeground = function(c) { - this.flags.collapsed || (null == this.lineargradient && this.createGradient(c), this.lineargradient && (c.lineWidth = 1, c.strokeStyle = this.properties.borderColor, c.fillStyle = this.lineargradient, this.properties.shadowSize ? (c.shadowColor = "#000", c.shadowOffsetX = 0, c.shadowOffsetY = 0, c.shadowBlur = this.properties.shadowSize) : c.shadowColor = "transparent", c.roundRect(0, 0, this.size[0] - 1, this.size[1] - 1, this.properties.shadowSize), c.fill(), c.shadowColor = "transparent", - c.stroke())); - }; - A.registerNodeType("widget/panel", E); -})(this); -(function(v) { - function c() { - this.addOutput("left_x_axis", "number"); - this.addOutput("left_y_axis", "number"); - this.addOutput("button_pressed", k.EVENT); - this.properties = {gamepad_index:0, threshold:0.1}; - this._left_axis = new Float32Array(2); - this._right_axis = new Float32Array(2); - this._triggers = new Float32Array(2); - this._previous_buttons = new Uint8Array(17); - this._current_buttons = new Uint8Array(17); - } - var k = v.LiteGraph; - c.title = "Gamepad"; - c.desc = "gets the input of the gamepad"; - c.CENTER = 0; - c.LEFT = 1; - c.RIGHT = 2; - c.UP = 4; - c.DOWN = 8; - c.zero = new Float32Array(2); - c.buttons = "a b x y lb rb lt rt back start ls rs home".split(" "); - c.prototype.onExecute = function() { - var h = this.getGamepad(), m = this.properties.threshold || 0.0; - h && (this._left_axis[0] = Math.abs(h.xbox.axes.lx) > m ? h.xbox.axes.lx : 0, this._left_axis[1] = Math.abs(h.xbox.axes.ly) > m ? h.xbox.axes.ly : 0, this._right_axis[0] = Math.abs(h.xbox.axes.rx) > m ? h.xbox.axes.rx : 0, this._right_axis[1] = Math.abs(h.xbox.axes.ry) > m ? h.xbox.axes.ry : 0, this._triggers[0] = Math.abs(h.xbox.axes.ltrigger) > m ? h.xbox.axes.ltrigger : 0, this._triggers[1] = Math.abs(h.xbox.axes.rtrigger) > m ? h.xbox.axes.rtrigger : 0); - if (this.outputs) { - for (m = 0; m < this.outputs.length; m++) { - var k = this.outputs[m]; - if (k.links && k.links.length) { - var g = null; - if (h) { - switch(k.name) { - case "left_axis": - g = this._left_axis; - break; - case "right_axis": - g = this._right_axis; - break; - case "left_x_axis": - g = this._left_axis[0]; - break; - case "left_y_axis": - g = this._left_axis[1]; - break; - case "right_x_axis": - g = this._right_axis[0]; - break; - case "right_y_axis": - g = this._right_axis[1]; - break; - case "trigger_left": - g = this._triggers[0]; - break; - case "trigger_right": - g = this._triggers[1]; - break; - case "a_button": - g = h.xbox.buttons.a ? 1 : 0; - break; - case "b_button": - g = h.xbox.buttons.b ? 1 : 0; - break; - case "x_button": - g = h.xbox.buttons.x ? 1 : 0; - break; - case "y_button": - g = h.xbox.buttons.y ? 1 : 0; - break; - case "lb_button": - g = h.xbox.buttons.lb ? 1 : 0; - break; - case "rb_button": - g = h.xbox.buttons.rb ? 1 : 0; - break; - case "ls_button": - g = h.xbox.buttons.ls ? 1 : 0; - break; - case "rs_button": - g = h.xbox.buttons.rs ? 1 : 0; - break; - case "hat_left": - g = h.xbox.hatmap & c.LEFT; - break; - case "hat_right": - g = h.xbox.hatmap & c.RIGHT; - break; - case "hat_up": - g = h.xbox.hatmap & c.UP; - break; - case "hat_down": - g = h.xbox.hatmap & c.DOWN; - break; - case "hat": - g = h.xbox.hatmap; - break; - case "start_button": - g = h.xbox.buttons.start ? 1 : 0; - break; - case "back_button": - g = h.xbox.buttons.back ? 1 : 0; - break; - case "button_pressed": - for (k = 0; k < this._current_buttons.length; ++k) { - this._current_buttons[k] && !this._previous_buttons[k] && this.triggerSlot(m, c.buttons[k]); - } - } - } else { - switch(k.name) { - case "button_pressed": - break; - case "left_axis": - case "right_axis": - g = c.zero; - break; - default: - g = 0; - } - } - this.setOutputData(m, g); - } - } - } - }; - c.mapping = {a:0, b:1, x:2, y:3, lb:4, rb:5, lt:6, rt:7, back:8, start:9, ls:10, rs:11}; - c.mapping_array = "a b x y lb rb lt rt back start ls rs".split(" "); - c.prototype.getGamepad = function() { - var h = navigator.getGamepads || navigator.webkitGetGamepads || navigator.mozGetGamepads; - if (!h) { - return null; - } - h = h.call(navigator); - this._previous_buttons.set(this._current_buttons); - for (var m = this.properties.gamepad_index; 4 > m; m++) { - if (h[m]) { - h = h[m]; - m = this.xbox_mapping; - m || (m = this.xbox_mapping = {axes:[], buttons:{}, hat:"", hatmap:c.CENTER}); - m.axes.lx = h.axes[0]; - m.axes.ly = h.axes[1]; - m.axes.rx = h.axes[2]; - m.axes.ry = h.axes[3]; - m.axes.ltrigger = h.buttons[6].value; - m.axes.rtrigger = h.buttons[7].value; - m.hat = ""; - m.hatmap = c.CENTER; - for (var k = 0; k < h.buttons.length; k++) { - if (this._current_buttons[k] = h.buttons[k].pressed, 12 > k) { - m.buttons[c.mapping_array[k]] = h.buttons[k].pressed, h.buttons[k].was_pressed && this.trigger(c.mapping_array[k] + "_button_event"); - } else { - switch(k) { - case 12: - h.buttons[k].pressed && (m.hat += "up", m.hatmap |= c.UP); - break; - case 13: - h.buttons[k].pressed && (m.hat += "down", m.hatmap |= c.DOWN); - break; - case 14: - h.buttons[k].pressed && (m.hat += "left", m.hatmap |= c.LEFT); - break; - case 15: - h.buttons[k].pressed && (m.hat += "right", m.hatmap |= c.RIGHT); - break; - case 16: - m.buttons.home = h.buttons[k].pressed; - } - } - } - h.xbox = m; - return h; - } - } - }; - c.prototype.onDrawBackground = function(c) { - if (!this.flags.collapsed) { - var m = this._left_axis, h = this._right_axis; - c.strokeStyle = "#88A"; - c.strokeRect(0.5 * (m[0] + 1) * this.size[0] - 4, 0.5 * (m[1] + 1) * this.size[1] - 4, 8, 8); - c.strokeStyle = "#8A8"; - c.strokeRect(0.5 * (h[0] + 1) * this.size[0] - 4, 0.5 * (h[1] + 1) * this.size[1] - 4, 8, 8); - m = this.size[1] / this._current_buttons.length; - c.fillStyle = "#AEB"; - for (h = 0; h < this._current_buttons.length; ++h) { - this._current_buttons[h] && c.fillRect(0, m * h, 6, m); - } - } - }; - c.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_button", "number"], ["back_button", "number"], ["a_button_event", k.EVENT], - ["b_button_event", k.EVENT], ["x_button_event", k.EVENT], ["y_button_event", k.EVENT], ["lb_button_event", k.EVENT], ["rb_button_event", k.EVENT], ["ls_button_event", k.EVENT], ["rs_button_event", k.EVENT], ["start_button_event", k.EVENT], ["back_button_event", k.EVENT], ["hat_left", "number"], ["hat_right", "number"], ["hat_up", "number"], ["hat_down", "number"], ["hat", "number"], ["button_pressed", k.EVENT]]; - }; - k.registerNodeType("input/gamepad", c); -})(this); -(function(v) { - function c() { - this.addInput("in", "*"); - this.size = [80, 30]; - } - function k() { - this.addInput("in"); - this.addOutput("out"); - this.size = [80, 30]; - } - function h() { - this.addInput("in"); - this.addOutput("out"); - } - function m() { - this.addInput("in", "number", {locked:!0}); - this.addOutput("out", "number", {locked:!0}); - this.addProperty("in", 0); - this.addProperty("in_min", 0); - this.addProperty("in_max", 1); - this.addProperty("out_min", 0); - this.addProperty("out_max", 1); - this.size = [80, 30]; - } - function t() { - this.addOutput("value", "number"); - this.addProperty("min", 0); - this.addProperty("max", 1); - this.size = [80, 30]; - } - function g() { - this.addInput("in", "number"); - this.addOutput("out", "number"); - this.addProperty("min", 0); - this.addProperty("max", 1); - this.addProperty("smooth", !0); - this.size = [90, 30]; - } - function D() { - this.addOutput("out", "number"); - this.addProperty("min_time", 1); - this.addProperty("max_time", 2); - this.addProperty("duration", 0.2); - this.size = [90, 30]; - this._blink_time = this._remaining_time = 0; - } - function B() { - this.addInput("in", "number"); - this.addOutput("out", "number"); - this.size = [80, 30]; - this.addProperty("min", 0); - this.addProperty("max", 1); - } - function x() { - this.properties = {f:0.5}; - this.addInput("A", "number"); - this.addInput("B", "number"); - this.addOutput("out", "number"); - } - function E() { - this.addInput("in", "number"); - this.addOutput("out", "number"); - this.size = [80, 30]; - } - function A() { - this.addInput("in", "number"); - this.addOutput("out", "number"); - this.size = [80, 30]; - } - function e() { - this.addInput("in", "number"); - this.addOutput("out", "number"); - this.size = [80, 30]; - } - function y() { - this.addInput("in", "number"); - this.addOutput("out", "number"); - this.size = [80, 30]; - this.properties = {A:0, B:1}; - } - function w() { - this.addInput("in", "number", {label:""}); - this.addOutput("out", "number", {label:""}); - this.size = [80, 30]; - this.addProperty("factor", 1); - } - function z() { - this.addInput("v", "boolean"); - this.addInput("A"); - this.addInput("B"); - this.addOutput("out"); - } - function l() { - this.addInput("in", "number"); - this.addOutput("out", "number"); - this.size = [80, 30]; - this.addProperty("samples", 10); - this._values = new Float32Array(10); - this._current = 0; - } - function p() { - this.addInput("in", "number"); - this.addOutput("out", "number"); - this.addProperty("factor", 0.1); - this.size = [80, 30]; - this._value = null; - } - function r() { - this.addInput("A", "number"); - this.addInput("B", "number"); - this.addOutput("=", "number"); - this.addProperty("A", 1); - this.addProperty("B", 1); - this.addProperty("OP", "+", "enum", {values:r.values}); - } - function f() { - this.addInput("A", "number"); - this.addInput("B", "number"); - this.addOutput("A==B", "boolean"); - this.addOutput("A!=B", "boolean"); - this.addProperty("A", 0); - this.addProperty("B", 0); - } - function a() { - this.addInput("A", "number"); - this.addInput("B", "number"); - this.addOutput("true", "boolean"); - this.addOutput("false", "boolean"); - this.addProperty("A", 1); - this.addProperty("B", 1); - this.addProperty("OP", ">", "enum", {values:a.values}); - this.size = [80, 60]; - } - function b() { - this.addInput("inc", "number"); - this.addOutput("total", "number"); - this.addProperty("increment", 1); - this.addProperty("value", 0); - } - function d() { - this.addInput("v", "number"); - this.addOutput("sin", "number"); - this.addProperty("amplitude", 1); - this.addProperty("offset", 0); - this.bgImageUrl = "nodes/imgs/icon-sin.png"; - } - function q() { - this.addInput("x", "number"); - this.addInput("y", "number"); - this.addOutput("", "number"); - this.properties = {x:1.0, y:1.0, formula:"x+y"}; - this.code_widget = this.addWidget("text", "F(x,y)", this.properties.formula, function(a, b, d) { - d.properties.formula = a; - }); - this.addWidget("toggle", "allow", u.allow_scripts, function(a) { - u.allow_scripts = a; - }); - this._func = null; - } - function n() { - this.addInput("vec2", "vec2"); - this.addOutput("x", "number"); - this.addOutput("y", "number"); - } - function H() { - this.addInputs([["x", "number"], ["y", "number"]]); - this.addOutput("vec2", "vec2"); - this.properties = {x:0, y:0}; - this._data = new Float32Array(2); - } - function G() { - this.addInput("vec3", "vec3"); - this.addOutput("x", "number"); - this.addOutput("y", "number"); - this.addOutput("z", "number"); - } - function L() { - this.addInputs([["x", "number"], ["y", "number"], ["z", "number"]]); - this.addOutput("vec3", "vec3"); - this.properties = {x:0, y:0, z:0}; - this._data = new Float32Array(3); - } - function J() { - this.addInput("vec4", "vec4"); - this.addOutput("x", "number"); - this.addOutput("y", "number"); - this.addOutput("z", "number"); - this.addOutput("w", "number"); - } - function M() { - this.addInputs([["x", "number"], ["y", "number"], ["z", "number"], ["w", "number"]]); - this.addOutput("vec4", "vec4"); - this.properties = {x:0, y:0, z:0, w:0}; - this._data = new Float32Array(4); - } - var u = v.LiteGraph; - c.title = "Converter"; - c.desc = "type A to type B"; - c.prototype.onExecute = function() { - var a = this.getInputData(0); - if (null != a && this.outputs) { - for (var b = 0; b < this.outputs.length; b++) { - var d = this.outputs[b]; - if (d.links && d.links.length) { - var c = null; - switch(d.name) { - case "number": - c = a.length ? a[0] : parseFloat(a); - break; - case "vec2": - case "vec3": - case "vec4": - c = 1; - switch(d.name) { - case "vec2": - c = 2; - break; - case "vec3": - c = 3; - break; - case "vec4": - c = 4; - }c = new Float32Array(c); - if (a.length) { - for (d = 0; d < a.length && d < c.length; d++) { - c[d] = a[d]; - } - } else { - c[0] = parseFloat(a); - } - } - this.setOutputData(b, c); - } - } - } - }; - c.prototype.onGetOutputs = function() { - return [["number", "number"], ["vec2", "vec2"], ["vec3", "vec3"], ["vec4", "vec4"]]; - }; - u.registerNodeType("math/converter", c); - k.title = "Bypass"; - k.desc = "removes the type"; - k.prototype.onExecute = function() { - var a = this.getInputData(0); - this.setOutputData(0, a); - }; - u.registerNodeType("math/bypass", k); - h.title = "to Number"; - h.desc = "Cast to number"; - h.prototype.onExecute = function() { - var a = this.getInputData(0); - this.setOutputData(0, Number(a)); - }; - u.registerNodeType("math/to_number", h); - m.title = "Range"; - m.desc = "Convert a number from one range to another"; - m.prototype.getTitle = function() { - return this.flags.collapsed ? (this._last_v || 0).toFixed(2) : this.title; - }; - m.prototype.onExecute = function() { - if (this.inputs) { - for (var a = 0; a < this.inputs.length; a++) { - var b = this.inputs[a], d = this.getInputData(a); - void 0 !== d && (this.properties[b.name] = d); - } - } - d = this.properties["in"]; - if (void 0 === d || null === d || d.constructor !== Number) { - d = 0; - } - a = this.properties.in_min; - b = this.properties.out_min; - this._last_v = (d - a) / (this.properties.in_max - a) * (this.properties.out_max - b) + b; - this.setOutputData(0, this._last_v); - }; - m.prototype.onDrawBackground = function(a) { - this.outputs[0].label = this._last_v ? this._last_v.toFixed(3) : "?"; - }; - m.prototype.onGetInputs = function() { - return [["in_min", "number"], ["in_max", "number"], ["out_min", "number"], ["out_max", "number"]]; - }; - u.registerNodeType("math/range", m); - t.title = "Rand"; - t.desc = "Random number"; - t.prototype.onExecute = function() { - if (this.inputs) { - for (var a = 0; a < this.inputs.length; a++) { - var b = this.inputs[a], d = this.getInputData(a); - void 0 !== d && (this.properties[b.name] = d); - } - } - a = this.properties.min; - this._last_v = Math.random() * (this.properties.max - a) + a; - this.setOutputData(0, this._last_v); - }; - t.prototype.onDrawBackground = function(a) { - this.outputs[0].label = (this._last_v || 0).toFixed(3); - }; - t.prototype.onGetInputs = function() { - return [["min", "number"], ["max", "number"]]; - }; - u.registerNodeType("math/rand", t); - g.title = "Noise"; - g.desc = "Random number with temporal continuity"; - g.data = null; - g.getValue = function(a, b) { - if (!g.data) { - g.data = new Float32Array(1024); - for (var d = 0; d < g.data.length; ++d) { - g.data[d] = Math.random(); - } - } - a %= 1024; - 0 > a && (a += 1024); - var c = Math.floor(a); - a -= c; - d = g.data[c]; - c = g.data[1023 == c ? 0 : c + 1]; - b && (a = a * a * a * (a * (6.0 * a - 15.0) + 10.0)); - return d * (1 - a) + c * a; - }; - g.prototype.onExecute = function() { - var a = this.getInputData(0) || 0; - a = g.getValue(a, this.properties.smooth); - var b = this.properties.min; - this._last_v = a * (this.properties.max - b) + b; - this.setOutputData(0, this._last_v); - }; - g.prototype.onDrawBackground = function(a) { - this.outputs[0].label = (this._last_v || 0).toFixed(3); - }; - u.registerNodeType("math/noise", g); - D.title = "Spikes"; - D.desc = "spike every random time"; - D.prototype.onExecute = function() { - var a = this.graph.elapsed_time; - this._remaining_time -= a; - this._blink_time -= a; - a = 0; - 0 < this._blink_time && (a = 1 / (Math.pow(this._blink_time / this.properties.duration * 8 - 4, 4) + 1)); - 0 > this._remaining_time ? (this._remaining_time = Math.random() * (this.properties.max_time - this.properties.min_time) + this.properties.min_time, this._blink_time = this.properties.duration, this.boxcolor = "#FFF") : this.boxcolor = "#000"; - this.setOutputData(0, a); - }; - u.registerNodeType("math/spikes", D); - B.title = "Clamp"; - B.desc = "Clamp number between min and max"; - B.prototype.onExecute = function() { - var a = this.getInputData(0); - null != a && (a = Math.max(this.properties.min, a), a = Math.min(this.properties.max, a), this.setOutputData(0, a)); - }; - B.prototype.getCode = function(a) { - a = ""; - this.isInputConnected(0) && (a += "clamp({{0}}," + this.properties.min + "," + this.properties.max + ")"); - return a; - }; - u.registerNodeType("math/clamp", B); - x.title = "Lerp"; - x.desc = "Linear Interpolation"; - x.prototype.onExecute = function() { - var a = this.getInputData(0); - null == a && (a = 0); - var b = this.getInputData(1); - null == b && (b = 0); - var d = this.properties.f, c = this.getInputData(2); - void 0 !== c && (d = c); - this.setOutputData(0, a * (1 - d) + b * d); - }; - x.prototype.onGetInputs = function() { - return [["f", "number"]]; - }; - u.registerNodeType("math/lerp", x); - E.title = "Abs"; - E.desc = "Absolute"; - E.prototype.onExecute = function() { - var a = this.getInputData(0); - null != a && this.setOutputData(0, Math.abs(a)); - }; - u.registerNodeType("math/abs", E); - A.title = "Floor"; - A.desc = "Floor number to remove fractional part"; - A.prototype.onExecute = function() { - var a = this.getInputData(0); - null != a && this.setOutputData(0, Math.floor(a)); - }; - u.registerNodeType("math/floor", A); - e.title = "Frac"; - e.desc = "Returns fractional part"; - e.prototype.onExecute = function() { - var a = this.getInputData(0); - null != a && this.setOutputData(0, a % 1); - }; - u.registerNodeType("math/frac", e); - y.title = "Smoothstep"; - y.desc = "Smoothstep"; - y.prototype.onExecute = function() { - var a = this.getInputData(0); - if (void 0 !== a) { - var b = this.properties.A; - a = Math.clamp((a - b) / (this.properties.B - b), 0.0, 1.0); - this.setOutputData(0, a * a * (3 - 2 * a)); - } - }; - u.registerNodeType("math/smoothstep", y); - w.title = "Scale"; - w.desc = "v * factor"; - w.prototype.onExecute = function() { - var a = this.getInputData(0); - null != a && this.setOutputData(0, a * this.properties.factor); - }; - u.registerNodeType("math/scale", w); - z.title = "Gate"; - z.desc = "if v is true, then outputs A, otherwise B"; - z.prototype.onExecute = function() { - var a = this.getInputData(0); - this.setOutputData(0, this.getInputData(a ? 1 : 2)); - }; - u.registerNodeType("math/gate", z); - l.title = "Average"; - l.desc = "Average Filter"; - l.prototype.onExecute = function() { - var a = this.getInputData(0); - null == a && (a = 0); - var b = this._values.length; - this._values[this._current % b] = a; - this._current += 1; - this._current > b && (this._current = 0); - for (var d = a = 0; d < b; ++d) { - a += this._values[d]; - } - this.setOutputData(0, a / b); - }; - l.prototype.onPropertyChanged = function(a, b) { - 1 > b && (b = 1); - this.properties.samples = Math.round(b); - a = this._values; - this._values = new Float32Array(this.properties.samples); - a.length <= this._values.length ? this._values.set(a) : this._values.set(a.subarray(0, this._values.length)); - }; - u.registerNodeType("math/average", l); - p.title = "TendTo"; - p.desc = "moves the output value always closer to the input"; - p.prototype.onExecute = function() { - var a = this.getInputData(0); - null == a && (a = 0); - var b = this.properties.factor; - this._value = null == this._value ? a : this._value * (1 - b) + a * b; - this.setOutputData(0, this._value); - }; - u.registerNodeType("math/tendTo", p); - r.values = "+ - * / % ^ max min".split(" "); - r.title = "Operation"; - r.desc = "Easy math operators"; - r["@OP"] = {type:"enum", title:"operation", values:r.values}; - r.size = [100, 60]; - r.prototype.getTitle = function() { - return "max" == this.properties.OP || "min" == this.properties.OP ? this.properties.OP + "(A,B)" : "A " + this.properties.OP + " B"; - }; - r.prototype.setValue = function(a) { - "string" == typeof a && (a = parseFloat(a)); - this.properties.value = a; - }; - r.prototype.onExecute = function() { - var a = this.getInputData(0), b = this.getInputData(1); - null != a ? this.properties.A = a : a = this.properties.A; - null != b ? this.properties.B = b : b = this.properties.B; - var d = 0; - switch(this.properties.OP) { - case "+": - d = a + b; - break; - case "-": - d = a - b; - break; - case "x": - case "X": - case "*": - d = a * b; - break; - case "/": - d = a / b; - break; - case "%": - d = a % b; - break; - case "^": - d = Math.pow(a, b); - break; - case "max": - d = Math.max(a, b); - break; - case "min": - d = Math.min(a, b); - break; - default: - console.warn("Unknown operation: " + this.properties.OP); - } - this.setOutputData(0, d); - }; - r.prototype.onDrawBackground = function(a) { - this.flags.collapsed || (a.font = "40px Arial", a.fillStyle = "#666", a.textAlign = "center", a.fillText(this.properties.OP, 0.5 * this.size[0], 0.5 * (this.size[1] + u.NODE_TITLE_HEIGHT)), a.textAlign = "left"); - }; - u.registerNodeType("math/operation", r); - u.registerSearchboxExtra("math/operation", "MAX", {properties:{OP:"max"}, title:"MAX()"}); - u.registerSearchboxExtra("math/operation", "MIN", {properties:{OP:"min"}, title:"MIN()"}); - f.title = "Compare"; - f.desc = "compares between two values"; - f.prototype.onExecute = function() { - var a = this.getInputData(0), b = this.getInputData(1); - void 0 !== a ? this.properties.A = a : a = this.properties.A; - void 0 !== b ? this.properties.B = b : b = this.properties.B; - for (var d = 0, c = this.outputs.length; d < c; ++d) { - var f = this.outputs[d]; - if (f.links && f.links.length) { - switch(f.name) { - case "A==B": - var e = a == b; - break; - case "A!=B": - e = a != b; - break; - case "A>B": - e = a > b; - break; - case "A=B": - e = a >= b; - } - this.setOutputData(d, e); - } - } - }; - f.prototype.onGetOutputs = function() { - return [["A==B", "boolean"], ["A!=B", "boolean"], ["A>B", "boolean"], ["A=B", "boolean"], ["A<=B", "boolean"]]; - }; - u.registerNodeType("math/compare", f); - u.registerSearchboxExtra("math/compare", "==", {outputs:[["A==B", "boolean"]], title:"A==B"}); - u.registerSearchboxExtra("math/compare", "!=", {outputs:[["A!=B", "boolean"]], title:"A!=B"}); - u.registerSearchboxExtra("math/compare", ">", {outputs:[["A>B", "boolean"]], title:"A>B"}); - u.registerSearchboxExtra("math/compare", "<", {outputs:[["A=", {outputs:[["A>=B", "boolean"]], title:"A>=B"}); - u.registerSearchboxExtra("math/compare", "<=", {outputs:[["A<=B", "boolean"]], title:"A<=B"}); - a.values = "> < == != <= >= || &&".split(" "); - a["@OP"] = {type:"enum", title:"operation", values:a.values}; - a.title = "Condition"; - a.desc = "evaluates condition between A and B"; - a.prototype.getTitle = function() { - return "A " + this.properties.OP + " B"; - }; - a.prototype.onExecute = function() { - var a = this.getInputData(0); - void 0 === a ? a = this.properties.A : this.properties.A = a; - var b = this.getInputData(1); - void 0 === b ? b = this.properties.B : this.properties.B = b; - var d = !0; - switch(this.properties.OP) { - case ">": - d = a > b; - break; - case "<": - d = a < b; - break; - case "==": - d = a == b; - break; - case "!=": - d = a != b; - break; - case "<=": - d = a <= b; - break; - case ">=": - d = a >= b; - break; - case "||": - d = a || b; - break; - case "&&": - d = a && b; - } - this.setOutputData(0, d); - this.setOutputData(1, !d); - }; - u.registerNodeType("math/condition", a); - b.title = "Accumulate"; - b.desc = "Increments a value every time"; - b.prototype.onExecute = function() { - null === this.properties.value && (this.properties.value = 0); - var a = this.getInputData(0); - this.properties.value = null !== a ? this.properties.value + a : this.properties.value + this.properties.increment; - this.setOutputData(0, this.properties.value); - }; - u.registerNodeType("math/accumulate", b); - d.title = "Trigonometry"; - d.desc = "Sin Cos Tan"; - d.prototype.onExecute = function() { - var a = this.getInputData(0); - null == a && (a = 0); - var b = this.properties.amplitude, d = this.findInputSlot("amplitude"); - -1 != d && (b = this.getInputData(d)); - var c = this.properties.offset; - d = this.findInputSlot("offset"); - -1 != d && (c = this.getInputData(d)); - d = 0; - for (var f = this.outputs.length; d < f; ++d) { - switch(this.outputs[d].name) { - case "sin": - var e = Math.sin(a); - break; - case "cos": - e = Math.cos(a); - break; - case "tan": - e = Math.tan(a); - break; - case "asin": - e = Math.asin(a); - break; - case "acos": - e = Math.acos(a); - break; - case "atan": - e = Math.atan(a); - } - this.setOutputData(d, b * e + c); - } - }; - d.prototype.onGetInputs = function() { - return [["v", "number"], ["amplitude", "number"], ["offset", "number"]]; - }; - d.prototype.onGetOutputs = function() { - return [["sin", "number"], ["cos", "number"], ["tan", "number"], ["asin", "number"], ["acos", "number"], ["atan", "number"]]; - }; - u.registerNodeType("math/trigonometry", d); - u.registerSearchboxExtra("math/trigonometry", "SIN()", {outputs:[["sin", "number"]], title:"SIN()"}); - u.registerSearchboxExtra("math/trigonometry", "COS()", {outputs:[["cos", "number"]], title:"COS()"}); - u.registerSearchboxExtra("math/trigonometry", "TAN()", {outputs:[["tan", "number"]], title:"TAN()"}); - q.title = "Formula"; - q.desc = "Compute formula"; - q.size = [160, 100]; - l.prototype.onPropertyChanged = function(a, b) { - "formula" == a && (this.code_widget.value = b); - }; - q.prototype.onExecute = function() { - if (u.allow_scripts) { - var a = this.getInputData(0), b = this.getInputData(1); - null != a ? this.properties.x = a : a = this.properties.x; - null != b ? this.properties.y = b : b = this.properties.y; - try { - this._func && this._func_code == this.properties.formula || (this._func = new Function("x", "y", "TIME", "return " + this.properties.formula), this._func_code = this.properties.formula); - var d = this._func(a, b, this.graph.globaltime); - this.boxcolor = null; - } catch (U) { - this.boxcolor = "red"; - } - this.setOutputData(0, d); - } - }; - q.prototype.getTitle = function() { - return this._func_code || "Formula"; - }; - q.prototype.onDrawBackground = function() { - var a = this.properties.formula; - this.outputs && this.outputs.length && (this.outputs[0].label = a); - }; - u.registerNodeType("math/formula", q); - n.title = "Vec2->XY"; - n.desc = "vector 2 to components"; - n.prototype.onExecute = function() { - var a = this.getInputData(0); - null != a && (this.setOutputData(0, a[0]), this.setOutputData(1, a[1])); - }; - u.registerNodeType("math3d/vec2-to-xy", n); - H.title = "XY->Vec2"; - H.desc = "components to vector2"; - H.prototype.onExecute = function() { - var a = this.getInputData(0); - null == a && (a = this.properties.x); - var b = this.getInputData(1); - null == b && (b = this.properties.y); - var d = this._data; - d[0] = a; - d[1] = b; - this.setOutputData(0, d); - }; - u.registerNodeType("math3d/xy-to-vec2", H); - G.title = "Vec3->XYZ"; - G.desc = "vector 3 to components"; - G.prototype.onExecute = function() { - var a = this.getInputData(0); - null != a && (this.setOutputData(0, a[0]), this.setOutputData(1, a[1]), this.setOutputData(2, a[2])); - }; - u.registerNodeType("math3d/vec3-to-xyz", G); - L.title = "XYZ->Vec3"; - L.desc = "components to vector3"; - L.prototype.onExecute = function() { - var a = this.getInputData(0); - null == a && (a = this.properties.x); - var b = this.getInputData(1); - null == b && (b = this.properties.y); - var d = this.getInputData(2); - null == d && (d = this.properties.z); - var c = this._data; - c[0] = a; - c[1] = b; - c[2] = d; - this.setOutputData(0, c); - }; - u.registerNodeType("math3d/xyz-to-vec3", L); - J.title = "Vec4->XYZW"; - J.desc = "vector 4 to components"; - J.prototype.onExecute = function() { - var a = this.getInputData(0); - null != a && (this.setOutputData(0, a[0]), this.setOutputData(1, a[1]), this.setOutputData(2, a[2]), this.setOutputData(3, a[3])); - }; - u.registerNodeType("math3d/vec4-to-xyzw", J); - M.title = "XYZW->Vec4"; - M.desc = "components to vector4"; - M.prototype.onExecute = function() { - var a = this.getInputData(0); - null == a && (a = this.properties.x); - var b = this.getInputData(1); - null == b && (b = this.properties.y); - var d = this.getInputData(2); - null == d && (d = this.properties.z); - var c = this.getInputData(3); - null == c && (c = this.properties.w); - var f = this._data; - f[0] = a; - f[1] = b; - f[2] = d; - f[3] = c; - this.setOutputData(0, f); - }; - u.registerNodeType("math3d/xyzw-to-vec4", M); - if (v.glMatrix) { - v = function() { - this.addInputs([["A", "quat"], ["B", "quat"], ["factor", "number"]]); - this.addOutput("slerp", "quat"); - this.addProperty("factor", 0.5); - this._value = quat.create(); - }; - var C = function() { - this.addInputs([["A", "quat"], ["B", "quat"]]); - this.addOutput("A*B", "quat"); - this._value = quat.create(); - }, K = function() { - this.addInputs([["vec3", "vec3"], ["quat", "quat"]]); - this.addOutput("result", "vec3"); - this.properties = {vec:[0, 0, 1]}; - }, N = function() { - this.addInputs([["degrees", "number"], ["axis", "vec3"]]); - this.addOutput("quat", "quat"); - this.properties = {angle:90.0, axis:vec3.fromValues(0, 1, 0)}; - this._value = quat.create(); - }, O = function() { - this.addOutput("quat", "quat"); - this.properties = {x:0, y:0, z:0, w:1}; - this._value = quat.create(); - }; - O.title = "Quaternion"; - O.desc = "quaternion"; - O.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); - }; - u.registerNodeType("math3d/quaternion", O); - N.title = "Rotation"; - N.desc = "quaternion rotation"; - N.prototype.onExecute = function() { - var a = this.getInputData(0); - null == a && (a = this.properties.angle); - var b = this.getInputData(1); - null == b && (b = this.properties.axis); - a = quat.setAxisAngle(this._value, b, 0.0174532925 * a); - this.setOutputData(0, a); - }; - u.registerNodeType("math3d/rotation", N); - K.title = "Rot. Vec3"; - K.desc = "rotate a point"; - K.prototype.onExecute = function() { - var a = this.getInputData(0); - null == a && (a = this.properties.vec); - var b = this.getInputData(1); - null == b ? this.setOutputData(a) : this.setOutputData(0, vec3.transformQuat(vec3.create(), a, b)); - }; - u.registerNodeType("math3d/rotate_vec3", K); - C.title = "Mult. Quat"; - C.desc = "rotate quaternion"; - C.prototype.onExecute = function() { - var a = this.getInputData(0); - if (null != a) { - var b = this.getInputData(1); - null != b && (a = quat.multiply(this._value, a, b), this.setOutputData(0, a)); - } - }; - u.registerNodeType("math3d/mult-quat", C); - v.title = "Quat Slerp"; - v.desc = "quaternion spherical interpolation"; - v.prototype.onExecute = function() { - var a = this.getInputData(0); - if (null != a) { - var b = this.getInputData(1); - if (null != b) { - var d = this.properties.factor; - null != this.getInputData(2) && (d = this.getInputData(2)); - a = quat.slerp(this._value, a, b, d); - this.setOutputData(0, a); - } - } - }; - u.registerNodeType("math3d/quat-slerp", v); - } -})(this); -(function(v) { - function c() { - this.addInput("sel", "number"); - this.addInput("A"); - this.addInput("B"); - this.addInput("C"); - this.addInput("D"); - this.addOutput("out"); - this.selected = 0; - } - function k() { - this.properties = {sequence:"A,B,C"}; - this.addInput("index", "number"); - this.addInput("seq"); - this.addOutput("out"); - this.index = 0; - this.values = this.properties.sequence.split(","); - } - var h = v.LiteGraph; - c.title = "Selector"; - c.desc = "selects an output"; - c.prototype.onDrawBackground = function(c) { - if (!this.flags.collapsed) { - c.fillStyle = "#AFB"; - var m = (this.selected + 1) * h.NODE_SLOT_HEIGHT + 6; - c.beginPath(); - c.moveTo(50, m); - c.lineTo(50, m + h.NODE_SLOT_HEIGHT); - c.lineTo(34, m + 0.5 * h.NODE_SLOT_HEIGHT); - c.fill(); - } - }; - c.prototype.onExecute = function() { - var c = this.getInputData(0); - if (null == c || c.constructor !== Number) { - c = 0; - } - this.selected = c = Math.round(c) % (this.inputs.length - 1); - c = this.getInputData(c + 1); - void 0 !== c && this.setOutputData(0, c); - }; - c.prototype.onGetInputs = function() { - return [["E", 0], ["F", 0], ["G", 0], ["H", 0]]; - }; - h.registerNodeType("logic/selector", c); - k.title = "Sequence"; - k.desc = "select one element from a sequence from a string"; - k.prototype.onPropertyChanged = function(c, h) { - "sequence" == c && (this.values = h.split(",")); - }; - k.prototype.onExecute = function() { - var c = this.getInputData(1); - c && c != this.current_sequence && (this.values = c.split(","), this.current_sequence = c); - c = this.getInputData(0); - null == c && (c = 0); - this.index = c = Math.round(c) % this.values.length; - this.setOutputData(0, this.values[c]); - }; - h.registerNodeType("logic/sequence", k); -})(this); -(function(v) { - function c() { - this.addOutput("tex", "Texture"); - this.addOutput("name", "string"); - this.properties = {name:"", filter:!0}; - this.size = [c.image_preview_size, c.image_preview_size]; - } - function k() { - this.addInput("Texture", "Texture"); - this.properties = {flipY:!1}; - this.size = [c.image_preview_size, c.image_preview_size]; - } - function h() { - this.addInput("Texture", "Texture"); - this.addOutput("tex", "Texture"); - this.addOutput("name", "string"); - this.properties = {name:"", generate_mipmaps:!1}; - } - function m() { - 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

\t\t

uv: tex. coords

color: texture colorB: textureB

time: scene time value: input value

For multiline you must type: result = ...

"; - this.properties = {value:1, pixelcode:"color + colorB * value", uvcode:"", precision:c.DEFAULT}; - this.has_error = !1; - } - function t() { - this.addOutput("out", "Texture"); - this.properties = {code:"", u_value:1, u_color:[1, 1, 1, 1], width:512, height:512, precision:c.DEFAULT}; - this.properties.code = "//time: time in seconds\n//texSize: vec2 with res\nuniform float u_value;\nuniform vec4 u_color;\n\nvoid main() {\n vec2 uv = v_coord;\n vec3 color = vec3(0.0);\n\t//your code here\n\tcolor.xy=uv;\n\ngl_FragColor = vec4(color, 1.0);\n}\n"; - this._uniforms = {u_value:1, u_color:vec4.create(), in_texture:0, texSize:vec2.create(), time:0}; - } - function g() { - 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:c.DEFAULT}; - } - function D() { - this.addInput("in", "Texture"); - this.addInput("warp", "Texture"); - this.addInput("factor", "number"); - this.addOutput("out", "Texture"); - this.properties = {factor:0.01, scale:[1, 1], offset:[0, 0], precision:c.DEFAULT}; - this._uniforms = {u_texture:0, u_textureB:1, u_factor:1, u_scale:vec2.create(), u_offset:vec2.create()}; - } - function B() { - this.addInput("Texture", "Texture"); - this.properties = {additive:!1, antialiasing:!1, filter:!0, disable_alpha:!1, gamma:1.0, viewport:[0, 0, 1, 1]}; - this.size[0] = 130; - } - function x() { - this.addInput("Texture", "Texture"); - this.addOutput("", "Texture"); - this.properties = {size:0, generate_mipmaps:!1, precision:c.DEFAULT}; - } - function E() { - this.addInput("Texture", "Texture"); - this.addOutput("", "Texture"); - this.properties = {iterations:1, generate_mipmaps:!1, precision:c.DEFAULT}; - } - function A() { - this.addInput("Texture", "Texture"); - this.addOutput("tex", "Texture"); - this.addOutput("avg", "vec4"); - this.addOutput("lum", "number"); - this.properties = {use_previous_frame:!0, high_quality:!1}; - this._uniforms = {u_texture:0, u_mipmap_offset:0}; - this._luminance = new Float32Array(4); - } - function e() { - this.addInput("in", "Texture"); - this.addInput("factor", "Number"); - this.addOutput("out", "Texture"); - this.properties = {factor:0.5}; - this._uniforms = {u_texture:0, u_textureB:1, u_factor:this.properties.factor}; - } - function y() { - this.addInput("in", "Texture"); - this.addOutput("avg", "Texture"); - this.addOutput("array", "Texture"); - this.properties = {samples:64, frames_interval:1}; - this._uniforms = {u_texture:0, u_textureB:1, u_samples:this.properties.samples, u_isamples:1 / this.properties.samples}; - this.frame = 0; - } - function w() { - this.addInput("Image", "image"); - this.addOutput("", "Texture"); - this.properties = {}; - } - function z() { - this.addInput("Texture", "Texture"); - this.addInput("LUT", "Texture"); - this.addInput("Intensity", "number"); - this.addOutput("", "Texture"); - this.properties = {enabled:!0, intensity:1, precision:c.DEFAULT, texture:null}; - z._shader || (z._shader = new GL.Shader(Shader.SCREEN_VERTEX_SHADER, z.pixel_shader)); - } - function l() { - this.addInput("Texture", "Texture"); - this.addOutput("R", "Texture"); - this.addOutput("G", "Texture"); - this.addOutput("B", "Texture"); - this.addOutput("A", "Texture"); - l._shader || (l._shader = new GL.Shader(Shader.SCREEN_VERTEX_SHADER, l.pixel_shader)); - } - function p() { - this.addInput("R", "Texture"); - this.addInput("G", "Texture"); - this.addInput("B", "Texture"); - this.addInput("A", "Texture"); - this.addOutput("Texture", "Texture"); - this.properties = {precision:c.DEFAULT, R:1, G:1, B:1, A:1}; - this._color = vec4.create(); - this._uniforms = {u_textureR:0, u_textureG:1, u_textureB:2, u_textureA:3, u_color:this._color}; - } - function r() { - this.addOutput("Texture", "Texture"); - this._tex_color = vec4.create(); - this.properties = {color:vec4.create(), precision:c.DEFAULT}; - } - function f() { - 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}; - f._shader || (f._shader = new GL.Shader(Shader.SCREEN_VERTEX_SHADER, f.pixel_shader)); - this._uniforms = {u_angle:0, u_colorA:vec3.create(), u_colorB:vec3.create()}; - } - function a() { - this.addInput("A", "Texture"); - this.addInput("B", "Texture"); - this.addInput("Mixer", "Texture"); - this.addOutput("Texture", "Texture"); - this.properties = {factor:0.5, size_from_biggest:!0, invert:!1, precision:c.DEFAULT}; - this._uniforms = {u_textureA:0, u_textureB:1, u_textureMix:2, u_mix:vec4.create()}; - } - function b() { - this.addInput("Tex.", "Texture"); - this.addOutput("Edges", "Texture"); - this.properties = {invert:!0, threshold:!1, factor:1, precision:c.DEFAULT}; - b._shader || (b._shader = new GL.Shader(Shader.SCREEN_VERTEX_SHADER, b.pixel_shader)); - } - function d() { - this.addInput("Texture", "Texture"); - this.addInput("Distance", "number"); - this.addInput("Range", "number"); - this.addOutput("Texture", "Texture"); - this.properties = {distance:100, range:50, only_depth:!1, high_precision:!1}; - this._uniforms = {u_texture:0, u_distance:100, u_range:50, u_camera_planes:null}; - } - function q() { - this.addInput("Texture", "Texture"); - this.addOutput("Texture", "Texture"); - this.properties = {precision:c.DEFAULT, invert:!1}; - this._uniforms = {u_texture:0, u_near:0.1, u_far:10000}; - } - function n() { - this.addInput("Texture", "Texture"); - this.addInput("Iterations", "number"); - this.addInput("Intensity", "number"); - this.addOutput("Blurred", "Texture"); - this.properties = {intensity:1, iterations:1, preserve_aspect:!1, scale:[1, 1], precision:c.DEFAULT}; - } - function H() { - this.addInput("in", "Texture"); - this.addInput("dirt", "Texture"); - this.addOutput("out", "Texture"); - this.addOutput("glow", "Texture"); - this.properties = {enabled:!0, intensity:1, persistence:0.99, iterations:16, threshold:0, scale:1, dirt_factor:0.5, precision:c.DEFAULT}; - this._textures = []; - this._uniforms = {u_intensity:1, u_texture:0, u_glow_texture:1, u_threshold:0, u_texel_size:vec2.create()}; - } - function G() { - this.addInput("Texture", "Texture"); - this.addOutput("Filtered", "Texture"); - this.properties = {intensity:1, radius:5}; - } - function L() { - this.addInput("Texture", "Texture"); - this.addOutput("Filtered", "Texture"); - this.properties = {sigma:1.4, k:1.6, p:21.7, epsilon:79, phi:0.017}; - } - function J() { - this.addOutput("Webcam", "Texture"); - this.properties = {texture_name:"", facingMode:"user"}; - this.boxcolor = "black"; - this.version = 0; - } - function M() { - this.addInput("in", "Texture"); - this.addInput("f", "number"); - this.addOutput("out", "Texture"); - this.properties = {enabled:!0, factor:1, precision:c.LOW}; - this._uniforms = {u_texture:0, u_factor:1}; - } - function u() { - this.addInput("in", "Texture"); - this.addOutput("out", "Texture"); - this.properties = {precision:c.LOW, split_channels:!1}; - this._values = new Uint8Array(1024); - this._values.fill(255); - this._curve_texture = null; - this._uniforms = {u_texture:0, u_curve:1, u_range:1.0}; - this._must_update = !0; - this._points = {RGB:[[0, 0], [1, 1]], R:[[0, 0], [1, 1]], G:[[0, 0], [1, 1]], B:[[0, 0], [1, 1]]}; - this.curve_editor = null; - this.addWidget("toggle", "Split Channels", !1, "split_channels"); - this.addWidget("combo", "Channel", "RGB", {values:["RGB", "R", "G", "B"]}); - this.curve_offset = 68; - this.size = [240, 160]; - } - function C() { - this.addInput("in", "Texture"); - this.addInput("exp", "number"); - this.addOutput("out", "Texture"); - this.properties = {exposition:1, precision:c.LOW}; - this._uniforms = {u_texture:0, u_exposition:1}; - } - function K() { - this.addInput("in", "Texture"); - this.addInput("avg", "number,Texture"); - this.addOutput("out", "Texture"); - this.properties = {enabled:!0, scale:1, gamma:1, average_lum:1, lum_white:1, precision:c.LOW}; - this._uniforms = {u_texture:0, u_lumwhite2:1, u_igamma:1, u_scale:1, u_average_lum:1}; - } - function N() { - this.addOutput("out", "Texture"); - this.properties = {width:512, height:512, seed:0, persistence:0.1, octaves:8, scale:1, offset:[0, 0], amplitude:1, precision:c.DEFAULT}; - this._key = 0; - this._texture = null; - this._uniforms = {u_persistence:0.1, u_seed:0, u_offset:vec2.create(), u_scale:1, u_viewport:vec2.create()}; - } - function O() { - this.addInput("v"); - this.addOutput("out", "Texture"); - this.properties = {code:"", width:512, height:512, clear:!0, precision:c.DEFAULT, use_html_canvas:!1}; - this._temp_texture = this._func = null; - } - function I() { - this.addInput("in", "Texture"); - this.addOutput("out", "Texture"); - this.properties = {key_color:vec3.fromValues(0, 1, 0), threshold:0.8, slope:0.2, precision:c.DEFAULT}; - } - function P() { - this.addInput("in", "texture"); - this.addInput("yaw", "number"); - this.addOutput("out", "texture"); - this.properties = {yaw:0}; - } - var F = v.LiteGraph; - v.LGraphTexture = null; - "undefined" != typeof GL && (LGraphCanvas.link_type_colors.Texture = "#987", v.LGraphTexture = c, c.title = "Texture", c.desc = "Texture", c.widgets_info = {name:{widget:"texture"}, filter:{widget:"checkbox"}}, c.loadTextureCallback = null, c.image_preview_size = 256, c.PASS_THROUGH = 1, c.COPY = 2, c.LOW = 3, c.HIGH = 4, c.REUSE = 5, c.DEFAULT = 2, c.MODE_VALUES = {"pass through":c.PASS_THROUGH, copy:c.COPY, low:c.LOW, high:c.HIGH, reuse:c.REUSE, default:c.DEFAULT}, c.getTexturesContainer = function() { - return gl.textures; - }, c.loadTexture = function(a, b) { - b = b || {}; - var d = a; - "http://" == d.substr(0, 7) && F.proxy && (d = F.proxy + d.substr(7)); - return c.getTexturesContainer()[a] = GL.Texture.fromURL(d, b); - }, c.getTexture = function(a) { - var b = this.getTexturesContainer(); - if (!b) { - throw "Cannot load texture, container of textures not found"; - } - b = b[a]; - return !b && a && ":" != a[0] ? this.loadTexture(a) : b; - }, c.getTargetTexture = function(a, b, d) { - if (!a) { - throw "LGraphTexture.getTargetTexture expects a reference texture"; - } - switch(d) { - case c.LOW: - d = gl.UNSIGNED_BYTE; - break; - case c.HIGH: - d = gl.HIGH_PRECISION_FORMAT; - break; - case c.REUSE: - return a; - default: - d = a ? a.type : gl.UNSIGNED_BYTE; - } - b && b.width == a.width && b.height == a.height && b.type == d || (b = new GL.Texture(a.width, a.height, {type:d, format:gl.RGBA, filter:gl.LINEAR})); - return b; - }, c.getTextureType = function(a, b) { - b = b ? b.type : gl.UNSIGNED_BYTE; - switch(a) { - case c.HIGH: - b = gl.HIGH_PRECISION_FORMAT; - break; - case c.LOW: - b = gl.UNSIGNED_BYTE; - } - return b; - }, c.getWhiteTexture = function() { - return this._white_texture ? this._white_texture : this._white_texture = GL.Texture.fromMemory(1, 1, [255, 255, 255, 255], {format:gl.RGBA, wrap:gl.REPEAT, filter:gl.NEAREST}); - }, c.getNoiseTexture = function() { - if (this._noise_texture) { - return this._noise_texture; - } - for (var a = new Uint8Array(1048576), b = 0; 1048576 > b; ++b) { - a[b] = 255 * Math.random(); - } - return this._noise_texture = a = GL.Texture.fromMemory(512, 512, a, {format:gl.RGBA, wrap:gl.REPEAT, filter:gl.NEAREST}); - }, c.prototype.onDropFile = function(a, b, d) { - a ? ("string" == typeof a ? a = GL.Texture.fromURL(a) : -1 != b.toLowerCase().indexOf(".dds") ? a = GL.Texture.fromDDSInMemory(a) : (a = new Blob([d]), a = URL.createObjectURL(a), a = GL.Texture.fromURL(a)), this._drop_texture = a, this.properties.name = b) : (this._drop_texture = null, this.properties.name = ""); - }, c.prototype.getExtraMenuOptions = function(a) { - var b = this; - if (this._drop_texture) { - return [{content:"Clear", callback:function() { - b._drop_texture = null; - b.properties.name = ""; - }}]; - } - }, c.prototype.onExecute = function() { - var a = null; - this.isOutputConnected(1) && (a = this.getInputData(0)); - !a && this._drop_texture && (a = this._drop_texture); - !a && this.properties.name && (a = c.getTexture(this.properties.name)); - if (a) { - this._last_tex = a; - !1 === this.properties.filter ? a.setParameter(gl.TEXTURE_MAG_FILTER, gl.NEAREST) : a.setParameter(gl.TEXTURE_MAG_FILTER, gl.LINEAR); - this.setOutputData(0, a); - this.setOutputData(1, a.fullpath || a.filename); - for (var b = 2; b < this.outputs.length; b++) { - var d = this.outputs[b]; - if (d) { - var f = null; - "width" == d.name ? f = a.width : "height" == d.name ? f = a.height : "aspect" == d.name && (f = a.width / a.height); - this.setOutputData(b, f); - } - } - } else { - this.setOutputData(0, null), this.setOutputData(1, ""); - } - }, c.prototype.onResourceRenamed = function(a, b) { - this.properties.name == a && (this.properties.name = b); - }, c.prototype.onDrawBackground = function(a) { - if (!(this.flags.collapsed || 20 >= this.size[1])) { - if (this._drop_texture && a.webgl) { - a.drawImage(this._drop_texture, 0, 0, this.size[0], this.size[1]); - } else { - if (this._last_preview_tex != this._last_tex) { - if (a.webgl) { - this._canvas = this._last_tex; - } else { - var b = c.generateLowResTexturePreview(this._last_tex); - if (!b) { - return; - } - this._last_preview_tex = this._last_tex; - this._canvas = cloneCanvas(b); - } - } - this._canvas && (a.save(), a.webgl || (a.translate(0, this.size[1]), a.scale(1, -1)), a.drawImage(this._canvas, 0, 0, this.size[0], this.size[1]), a.restore()); - } - } - }, c.generateLowResTexturePreview = function(a) { - if (!a) { - return null; - } - var b = c.image_preview_size, d = a; - if (a.format == gl.DEPTH_COMPONENT) { - return null; - } - if (a.width > b || a.height > b) { - d = this._preview_temp_tex, this._preview_temp_tex || (this._preview_temp_tex = d = new GL.Texture(b, b, {minFilter:gl.NEAREST})), a.copyTo(d); - } - a = this._preview_canvas; - a || (this._preview_canvas = a = createCanvas(b, b)); - d && d.toCanvas(a); - return a; - }, c.prototype.getResources = function(a) { - this.properties.name && (a[this.properties.name] = GL.Texture); - return a; - }, c.prototype.onGetInputs = function() { - return [["in", "Texture"]]; - }, c.prototype.onGetOutputs = function() { - return [["width", "number"], ["height", "number"], ["aspect", "number"]]; - }, c.replaceCode = function(a, b) { - return a.replace(/\{\{[a-zA-Z0-9_]*\}\}/g, function(a) { - a = a.replace(/[\{\}]/g, ""); - return b[a] || ""; - }); - }, F.registerNodeType("texture/texture", c), k.title = "Preview", k.desc = "Show a texture in the graph canvas", k.allow_preview = !1, k.prototype.onDrawBackground = function(a) { - if (!this.flags.collapsed && (a.webgl || k.allow_preview)) { - var b = this.getInputData(0); - b && (b = !b.handle && a.webgl ? b : c.generateLowResTexturePreview(b), a.save(), this.properties.flipY && (a.translate(0, this.size[1]), a.scale(1, -1)), a.drawImage(b, 0, 0, this.size[0], this.size[1]), a.restore()); - } - }, F.registerNodeType("texture/preview", k), h.title = "Save", h.desc = "Save a texture in the repository", h.prototype.getPreviewTexture = function() { - return this._texture; - }, h.prototype.onExecute = function() { - var a = this.getInputData(0); - a && (this.properties.generate_mipmaps && (a.bind(0), a.setParameter(gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR), gl.generateMipmap(a.texture_type), a.unbind(0)), this.properties.name && (c.storeTexture ? c.storeTexture(this.properties.name, a) : c.getTexturesContainer()[this.properties.name] = a), this._texture = a, this.setOutputData(0, a), this.setOutputData(1, this.properties.name)); - }, F.registerNodeType("texture/save", h), m.widgets_info = {uvcode:{widget:"code"}, pixelcode:{widget:"code"}, precision:{widget:"combo", values:c.MODE_VALUES}}, m.title = "Operation", m.desc = "Texture shader operation", m.presets = {}, m.prototype.getExtraMenuOptions = function(a) { - var b = this; - return [{content:b.properties.show ? "Hide Texture" : "Show Texture", callback:function() { - b.properties.show = !b.properties.show; - }}]; - }, m.prototype.onPropertyChanged = function() { - this.has_error = !1; - }, m.prototype.onDrawBackground = function(a) { - this.flags.collapsed || 20 >= this.size[1] || !this.properties.show || !this._tex || this._tex.gl != a || (a.save(), a.drawImage(this._tex, 0, 0, this.size[0], this.size[1]), a.restore()); - }, m.prototype.onExecute = function() { - var a = this.getInputData(0); - if (this.isOutputConnected(0)) { - if (this.properties.precision === c.PASS_THROUGH) { - this.setOutputData(0, a); - } else { - var b = this.getInputData(1); - if (this.properties.uvcode || this.properties.pixelcode) { - var d = 512, f = 512; - a ? (d = a.width, f = a.height) : b && (d = b.width, f = b.height); - b || (b = GL.Texture.getWhiteTexture()); - var e = c.getTextureType(this.properties.precision, a); - this._tex = a || this._tex ? c.getTargetTexture(a || this._tex, this._tex, this.properties.precision) : new GL.Texture(d, f, {type:e, format:gl.RGBA, filter:gl.LINEAR}); - e = ""; - this.properties.uvcode && (e = "uv = " + this.properties.uvcode, -1 != this.properties.uvcode.indexOf(";") && (e = this.properties.uvcode)); - var g = ""; - this.properties.pixelcode && (g = "result = " + this.properties.pixelcode, -1 != this.properties.pixelcode.indexOf(";") && (g = this.properties.pixelcode)); - var l = this._shader; - if (!(this.has_error || l && this._shader_code == e + "|" + g)) { - var p = c.replaceCode(m.pixel_shader, {UV_CODE:e, PIXEL_CODE:g}); - try { - l = new GL.Shader(Shader.SCREEN_VERTEX_SHADER, p), this.boxcolor = "#00FF00"; - } catch (T) { - GL.Shader.dumpErrorToConsole(T, Shader.SCREEN_VERTEX_SHADER, p); - this.boxcolor = "#FF0000"; - this.has_error = !0; - return; - } - this._shader = l; - this._shader_code = e + "|" + g; - } - if (this._shader) { - var h = this.getInputData(2); - null != h ? this.properties.value = h : h = parseFloat(this.properties.value); - var k = this.graph.getTime(); - this._tex.drawTo(function() { - gl.disable(gl.DEPTH_TEST); - gl.disable(gl.CULL_FACE); - gl.disable(gl.BLEND); - a && a.bind(0); - b && b.bind(1); - var c = Mesh.getScreenQuad(); - l.uniforms({u_texture:0, u_textureB:1, value:h, texSize:[d, f], time:k}).draw(c); - }); - this.setOutputData(0, this._tex); - } - } - } - } - }, m.pixel_shader = "precision highp float;\n\t\t\n\t\tuniform sampler2D u_texture;\n\t\tuniform sampler2D u_textureB;\n\t\tvarying vec2 v_coord;\n\t\tuniform vec2 texSize;\n\t\tuniform float time;\n\t\tuniform float value;\n\t\t\n\t\tvoid main() {\n\t\t\tvec2 uv = v_coord;\n\t\t\t{{UV_CODE}};\n\t\t\tvec4 color4 = texture2D(u_texture, uv);\n\t\t\tvec3 color = color4.rgb;\n\t\t\tvec4 color4B = texture2D(u_textureB, uv);\n\t\t\tvec3 colorB = color4B.rgb;\n\t\t\tvec3 result = color;\n\t\t\tfloat alpha = 1.0;\n\t\t\t{{PIXEL_CODE}};\n\t\t\tgl_FragColor = vec4(result, alpha);\n\t\t}\n\t\t", - m.registerPreset = function(a, b) { - m.presets[a] = b; - }, m.registerPreset("", ""), m.registerPreset("bypass", "color"), m.registerPreset("add", "color + colorB * value"), m.registerPreset("substract", "(color - colorB) * value"), m.registerPreset("mate", "mix( color, colorB, color4B.a * value)"), m.registerPreset("invert", "vec3(1.0) - color"), m.registerPreset("multiply", "color * colorB * value"), m.registerPreset("divide", "(color / colorB) / value"), m.registerPreset("difference", "abs(color - colorB) * value"), m.registerPreset("max", "max(color, colorB) * value"), - m.registerPreset("min", "min(color, colorB) * value"), m.registerPreset("displace", "texture2D(u_texture, uv + (colorB.xy - vec2(0.5)) * value).xyz"), m.registerPreset("grayscale", "vec3(color.x + color.y + color.z) * value / 3.0"), m.registerPreset("saturation", "mix( vec3(color.x + color.y + color.z) / 3.0, color, value )"), m.registerPreset("threshold", "vec3(color.x > colorB.x * value ? 1.0 : 0.0,color.y > colorB.y * value ? 1.0 : 0.0,color.z > colorB.z * value ? 1.0 : 0.0)"), m.prototype.onInspect = - function(a) { - var b = this; - a.addCombo("Presets", "", {values:Object.keys(m.presets), callback:function(d) { - var c = m.presets[d]; - c && (b.setProperty("pixelcode", c), b.title = d, a.refresh()); - }}); - }, F.registerNodeType("texture/operation", m), t.title = "Shader", t.desc = "Texture shader", t.widgets_info = {code:{type:"code"}, precision:{widget:"combo", values:c.MODE_VALUES}}, t.prototype.onPropertyChanged = function(a, b) { - if ("code" == a && (a = this.getShader())) { - b = a.uniformInfo; - if (this.inputs) { - for (var d = {}, c = 0; c < this.inputs.length; ++c) { - var f = this.getInputInfo(c); - f && (b[f.name] && !d[f.name] ? d[f.name] = !0 : (this.removeInput(c), c--)); - } - } - for (c in b) { - if (f = a.uniformInfo[c], null !== f.loc && "time" != c) { - if (this._shader.samplers[c]) { - b = "texture"; - } else { - switch(f.size) { - case 1: - b = "number"; - break; - case 2: - b = "vec2"; - break; - case 3: - b = "vec3"; - break; - case 4: - b = "vec4"; - break; - case 9: - b = "mat3"; - break; - case 16: - b = "mat4"; - break; - default: - continue; - } - } - d = this.findInputSlot(c); - if (-1 != d && (f = this.getInputInfo(d))) { - if (f.type == b) { - continue; - } - this.removeInput(d, b); - } - this.addInput(c, b); - } - } - } - }, t.prototype.getShader = function() { - if (this._shader && this._shader_code == this.properties.code) { - return this._shader; - } - this._shader_code = this.properties.code; - this._shader = new GL.Shader(Shader.SCREEN_VERTEX_SHADER, t.pixel_shader + this.properties.code), this.boxcolor = "green"; - return this._shader; - }, t.prototype.onExecute = function() { - if (this.isOutputConnected(0)) { - var a = this.getShader(); - if (a) { - var b = 0, d = null; - if (this.inputs) { - for (var f = 0; f < this.inputs.length; ++f) { - var e = this.getInputInfo(f), g = this.getInputData(f); - null != g && (g.constructor === GL.Texture && (g.bind(b), d || (d = g), g = b, b++), a.setUniform(e.name, g)); - } - } - var l = this._uniforms; - b = c.getTextureType(this.properties.precision, d); - f = this.properties.width | 0; - e = this.properties.height | 0; - 0 == f && (f = d ? d.width : gl.canvas.width); - 0 == e && (e = d ? d.height : gl.canvas.height); - l.texSize[0] = f; - l.texSize[1] = e; - l.time = this.graph.getTime(); - l.u_value = this.properties.u_value; - l.u_color.set(this.properties.u_color); - this._tex && this._tex.type == b && this._tex.width == f && this._tex.height == e || (this._tex = new GL.Texture(f, e, {type:b, format:gl.RGBA, filter:gl.LINEAR})); - this._tex.drawTo(function() { - a.uniforms(l).draw(GL.Mesh.getScreenQuad()); - }); - this.setOutputData(0, this._tex); - } - } - }, t.pixel_shader = "precision highp float;\n\t\t\n\t\tvarying vec2 v_coord;\n\t\tuniform float time;\n", F.registerNodeType("texture/shader", t), g.widgets_info = {precision:{widget:"combo", values:c.MODE_VALUES}}, g.title = "Scale/Offset", g.desc = "Applies an scaling and offseting", g.prototype.onExecute = function() { - var a = this.getInputData(0); - if (this.isOutputConnected(0) && a) { - if (this.properties.precision === c.PASS_THROUGH) { - this.setOutputData(0, a); - } else { - var b = a.width, d = a.height, f = this.precision === c.LOW ? gl.UNSIGNED_BYTE : gl.HIGH_PRECISION_FORMAT; - this.precision === c.DEFAULT && (f = a.type); - this._tex && this._tex.width == b && this._tex.height == d && this._tex.type == f || (this._tex = new GL.Texture(b, d, {type:f, format:gl.RGBA, filter:gl.LINEAR})); - var e = this._shader; - e || (e = new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER, g.pixel_shader)); - var l = this.getInputData(1); - l ? (this.properties.scale[0] = l[0], this.properties.scale[1] = l[1]) : l = this.properties.scale; - var p = this.getInputData(2); - p ? (this.properties.offset[0] = p[0], this.properties.offset[1] = p[1]) : p = this.properties.offset; - this._tex.drawTo(function() { - gl.disable(gl.DEPTH_TEST); - gl.disable(gl.CULL_FACE); - gl.disable(gl.BLEND); - a.bind(0); - var b = Mesh.getScreenQuad(); - e.uniforms({u_texture:0, u_scale:l, u_offset:p}).draw(b); - }); - this.setOutputData(0, this._tex); - } - } - }, g.pixel_shader = "precision highp float;\n\t\t\n\t\tuniform sampler2D u_texture;\n\t\tuniform sampler2D u_textureB;\n\t\tvarying vec2 v_coord;\n\t\tuniform vec2 u_scale;\n\t\tuniform vec2 u_offset;\n\t\t\n\t\tvoid main() {\n\t\t\tvec2 uv = v_coord;\n\t\t\tuv = uv / u_scale - u_offset;\n\t\t\tgl_FragColor = texture2D(u_texture, uv);\n\t\t}\n\t\t", F.registerNodeType("texture/scaleOffset", g), D.widgets_info = {precision:{widget:"combo", values:c.MODE_VALUES}}, D.title = "Warp", D.desc = "Texture warp operation", - D.prototype.onExecute = function() { - var a = this.getInputData(0); - if (this.isOutputConnected(0)) { - if (this.properties.precision === c.PASS_THROUGH) { - this.setOutputData(0, a); - } else { - var b = this.getInputData(1), d = 512, f = 512; - a ? (d = a.width, f = a.height) : b && (d = b.width, f = b.height); - this._tex = a || this._tex ? c.getTargetTexture(a || this._tex, this._tex, this.properties.precision) : new GL.Texture(d, f, {type:this.precision === c.LOW ? gl.UNSIGNED_BYTE : gl.HIGH_PRECISION_FORMAT, format:gl.RGBA, filter:gl.LINEAR}); - var e = this._shader; - e || (e = new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER, D.pixel_shader)); - d = this.getInputData(2); - null != d ? this.properties.factor = d : d = parseFloat(this.properties.factor); - var g = this._uniforms; - g.u_factor = d; - g.u_scale.set(this.properties.scale); - g.u_offset.set(this.properties.offset); - this._tex.drawTo(function() { - gl.disable(gl.DEPTH_TEST); - gl.disable(gl.CULL_FACE); - gl.disable(gl.BLEND); - a && a.bind(0); - b && b.bind(1); - var d = Mesh.getScreenQuad(); - e.uniforms(g).draw(d); - }); - this.setOutputData(0, this._tex); - } - } - }, D.pixel_shader = "precision highp float;\n\t\t\n\t\tuniform sampler2D u_texture;\n\t\tuniform sampler2D u_textureB;\n\t\tvarying vec2 v_coord;\n\t\tuniform float u_factor;\n\t\tuniform vec2 u_scale;\n\t\tuniform vec2 u_offset;\n\t\t\n\t\tvoid main() {\n\t\t\tvec2 uv = v_coord;\n\t\t\tuv += ( texture2D(u_textureB, uv).rg - vec2(0.5)) * u_factor * u_scale + u_offset;\n\t\t\tgl_FragColor = texture2D(u_texture, uv);\n\t\t}\n\t\t", F.registerNodeType("texture/warp", D), B.title = "to Viewport", B.desc = - "Texture to viewport", B._prev_viewport = new Float32Array(4), B.prototype.onExecute = function() { - var a = this.getInputData(0); - if (a) { - this.properties.disable_alpha ? gl.disable(gl.BLEND) : (gl.enable(gl.BLEND), this.properties.additive ? gl.blendFunc(gl.SRC_ALPHA, gl.ONE) : gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)); - gl.disable(gl.DEPTH_TEST); - var b = this.properties.gamma || 1.0; - this.isInputConnected(1) && (b = this.getInputData(1)); - a.setParameter(gl.TEXTURE_MAG_FILTER, this.properties.filter ? gl.LINEAR : gl.NEAREST); - var d = B._prev_viewport; - d.set(gl.viewport_data); - var c = this.properties.viewport; - gl.viewport(d[0] + d[2] * c[0], d[1] + d[3] * c[1], d[2] * c[2], d[3] * c[3]); - gl.getViewport(); - this.properties.antialiasing ? (B._shader || (B._shader = new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER, B.aa_pixel_shader)), c = Mesh.getScreenQuad(), a.bind(0), B._shader.uniforms({u_texture:0, uViewportSize:[a.width, a.height], u_igamma:1 / b, inverseVP:[1 / a.width, 1 / a.height]}).draw(c)) : 1.0 != b ? (B._gamma_shader || (B._gamma_shader = new GL.Shader(Shader.SCREEN_VERTEX_SHADER, B.gamma_pixel_shader)), a.toViewport(B._gamma_shader, {u_texture:0, u_igamma:1 / b})) : a.toViewport(); - gl.viewport(d[0], d[1], d[2], d[3]); - } - }, B.prototype.onGetInputs = function() { - return [["gamma", "number"]]; - }, B.aa_pixel_shader = "precision highp float;\n\t\tprecision highp float;\n\t\tvarying vec2 v_coord;\n\t\tuniform sampler2D u_texture;\n\t\tuniform vec2 uViewportSize;\n\t\tuniform vec2 inverseVP;\n\t\tuniform float u_igamma;\n\t\t#define FXAA_REDUCE_MIN (1.0/ 128.0)\n\t\t#define FXAA_REDUCE_MUL (1.0 / 8.0)\n\t\t#define FXAA_SPAN_MAX 8.0\n\t\t\n\t\t/* from mitsuhiko/webgl-meincraft based on the code on geeks3d.com */\n\t\tvec4 applyFXAA(sampler2D tex, vec2 fragCoord)\n\t\t{\n\t\t\tvec4 color = vec4(0.0);\n\t\t\t/*vec2 inverseVP = vec2(1.0 / uViewportSize.x, 1.0 / uViewportSize.y);*/\n\t\t\tvec3 rgbNW = texture2D(tex, (fragCoord + vec2(-1.0, -1.0)) * inverseVP).xyz;\n\t\t\tvec3 rgbNE = texture2D(tex, (fragCoord + vec2(1.0, -1.0)) * inverseVP).xyz;\n\t\t\tvec3 rgbSW = texture2D(tex, (fragCoord + vec2(-1.0, 1.0)) * inverseVP).xyz;\n\t\t\tvec3 rgbSE = texture2D(tex, (fragCoord + vec2(1.0, 1.0)) * inverseVP).xyz;\n\t\t\tvec3 rgbM = texture2D(tex, fragCoord * inverseVP).xyz;\n\t\t\tvec3 luma = vec3(0.299, 0.587, 0.114);\n\t\t\tfloat lumaNW = dot(rgbNW, luma);\n\t\t\tfloat lumaNE = dot(rgbNE, luma);\n\t\t\tfloat lumaSW = dot(rgbSW, luma);\n\t\t\tfloat lumaSE = dot(rgbSE, luma);\n\t\t\tfloat lumaM = dot(rgbM, luma);\n\t\t\tfloat lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE)));\n\t\t\tfloat lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE)));\n\t\t\t\n\t\t\tvec2 dir;\n\t\t\tdir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE));\n\t\t\tdir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE));\n\t\t\t\n\t\t\tfloat dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) * (0.25 * FXAA_REDUCE_MUL), FXAA_REDUCE_MIN);\n\t\t\t\n\t\t\tfloat rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce);\n\t\t\tdir = min(vec2(FXAA_SPAN_MAX, FXAA_SPAN_MAX), max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX), dir * rcpDirMin)) * inverseVP;\n\t\t\t\n\t\t\tvec3 rgbA = 0.5 * (texture2D(tex, fragCoord * inverseVP + dir * (1.0 / 3.0 - 0.5)).xyz + \n\t\t\t\ttexture2D(tex, fragCoord * inverseVP + dir * (2.0 / 3.0 - 0.5)).xyz);\n\t\t\tvec3 rgbB = rgbA * 0.5 + 0.25 * (texture2D(tex, fragCoord * inverseVP + dir * -0.5).xyz + \n\t\t\t\ttexture2D(tex, fragCoord * inverseVP + dir * 0.5).xyz);\n\t\t\t\n\t\t\t//return vec4(rgbA,1.0);\n\t\t\tfloat lumaB = dot(rgbB, luma);\n\t\t\tif ((lumaB < lumaMin) || (lumaB > lumaMax))\n\t\t\t\tcolor = vec4(rgbA, 1.0);\n\t\t\telse\n\t\t\t\tcolor = vec4(rgbB, 1.0);\n\t\t\tif(u_igamma != 1.0)\n\t\t\t\tcolor.xyz = pow( color.xyz, vec3(u_igamma) );\n\t\t\treturn color;\n\t\t}\n\t\t\n\t\tvoid main() {\n\t\t gl_FragColor = applyFXAA( u_texture, v_coord * uViewportSize) ;\n\t\t}\n\t\t", - B.gamma_pixel_shader = "precision highp float;\n\t\tprecision highp float;\n\t\tvarying vec2 v_coord;\n\t\tuniform sampler2D u_texture;\n\t\tuniform float u_igamma;\n\t\tvoid main() {\n\t\t\tvec4 color = texture2D( u_texture, v_coord);\n\t\t\tcolor.xyz = pow(color.xyz, vec3(u_igamma) );\n\t\t gl_FragColor = color;\n\t\t}\n\t\t", F.registerNodeType("texture/toviewport", B), x.title = "Copy", x.desc = "Copy Texture", x.widgets_info = {size:{widget:"combo", values:[0, 32, 64, 128, 256, 512, 1024, - 2048]}, precision:{widget:"combo", values:c.MODE_VALUES}}, x.prototype.onExecute = function() { - var a = this.getInputData(0); - if ((a || this._temp_texture) && this.isOutputConnected(0)) { - if (a) { - var b = a.width, d = a.height; - 0 != this.properties.size && (d = b = this.properties.size); - var f = this._temp_texture, e = a.type; - this.properties.precision === c.LOW ? e = gl.UNSIGNED_BYTE : this.properties.precision === c.HIGH && (e = gl.HIGH_PRECISION_FORMAT); - f && f.width == b && f.height == d && f.type == e || (f = gl.LINEAR, this.properties.generate_mipmaps && isPowerOfTwo(b) && isPowerOfTwo(d) && (f = gl.LINEAR_MIPMAP_LINEAR), this._temp_texture = new GL.Texture(b, d, {type:e, format:gl.RGBA, minFilter:f, magFilter:gl.LINEAR})); - a.copyTo(this._temp_texture); - this.properties.generate_mipmaps && (this._temp_texture.bind(0), gl.generateMipmap(this._temp_texture.texture_type), this._temp_texture.unbind(0)); - } - this.setOutputData(0, this._temp_texture); - } - }, F.registerNodeType("texture/copy", x), E.title = "Downsample", E.desc = "Downsample Texture", E.widgets_info = {iterations:{type:"number", step:1, precision:0, min:0}, precision:{widget:"combo", values:c.MODE_VALUES}}, E.prototype.onExecute = function() { - var a = this.getInputData(0); - if ((a || this._temp_texture) && this.isOutputConnected(0) && a && a.texture_type === GL.TEXTURE_2D) { - if (1 > this.properties.iterations) { - this.setOutputData(0, a); - } else { - var b = E._shader; - b || (E._shader = b = new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER, E.pixel_shader)); - var d = a.width | 0, f = a.height | 0, e = a.type; - this.properties.precision === c.LOW ? e = gl.UNSIGNED_BYTE : this.properties.precision === c.HIGH && (e = gl.HIGH_PRECISION_FORMAT); - var g = this.properties.iterations || 1, l = a, p = []; - e = {type:e, format:a.format}; - var h = vec2.create(), m = {u_offset:h}; - this._texture && GL.Texture.releaseTemporary(this._texture); - for (var k = 0; k < g; ++k) { - h[0] = 1 / d; - h[1] = 1 / f; - d = d >> 1 || 0; - f = f >> 1 || 0; - a = GL.Texture.getTemporary(d, f, e); - p.push(a); - l.setParameter(GL.TEXTURE_MAG_FILTER, GL.NEAREST); - l.copyTo(a, b, m); - if (1 == d && 1 == f) { - break; - } - l = a; - } - this._texture = p.pop(); - for (k = 0; k < p.length; ++k) { - GL.Texture.releaseTemporary(p[k]); - } - this.properties.generate_mipmaps && (this._texture.bind(0), gl.generateMipmap(this._texture.texture_type), this._texture.unbind(0)); - this.setOutputData(0, this._texture); - } - } - }, E.pixel_shader = "precision highp float;\n\t\tprecision highp float;\n\t\tuniform sampler2D u_texture;\n\t\tuniform vec2 u_offset;\n\t\tvarying vec2 v_coord;\n\t\t\n\t\tvoid main() {\n\t\t\tvec4 color = texture2D(u_texture, v_coord );\n\t\t\tcolor += texture2D(u_texture, v_coord + vec2( u_offset.x, 0.0 ) );\n\t\t\tcolor += texture2D(u_texture, v_coord + vec2( 0.0, u_offset.y ) );\n\t\t\tcolor += texture2D(u_texture, v_coord + vec2( u_offset.x, u_offset.y ) );\n\t\t gl_FragColor = color * 0.25;\n\t\t}\n\t\t", - F.registerNodeType("texture/downsample", E), A.title = "Average", A.desc = "Compute a partial average (32 random samples) of a texture and stores it as a 1x1 pixel texture.\n If high_quality is true, then it generates the mipmaps first and reads from the lower one.", A.prototype.onExecute = function() { - this.properties.use_previous_frame || this.updateAverage(); - var a = this._luminance; - this.setOutputData(0, this._temp_texture); - this.setOutputData(1, a); - this.setOutputData(2, (a[0] + a[1] + a[2]) / 3); - }, A.prototype.onPreRenderExecute = function() { - this.updateAverage(); - }, A.prototype.updateAverage = function() { - var a = this.getInputData(0); - if (a && (this.isOutputConnected(0) || this.isOutputConnected(1) || this.isOutputConnected(2))) { - if (!A._shader) { - A._shader = new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER, A.pixel_shader); - for (var b = new Float32Array(16), d = 0; d < b.length; ++d) { - b[d] = Math.random(); - } - A._shader.uniforms({u_samples_a:b.subarray(0, 16), u_samples_b:b.subarray(16, 32)}); - } - d = this._temp_texture; - b = gl.UNSIGNED_BYTE; - a.type != b && (b = gl.FLOAT); - d && d.type == b || (this._temp_texture = new GL.Texture(1, 1, {type:b, format:gl.RGBA, filter:gl.NEAREST})); - this._uniforms.u_mipmap_offset = 0; - this.properties.high_quality && (this._temp_pot2_texture && this._temp_pot2_texture.type == b || (this._temp_pot2_texture = new GL.Texture(512, 512, {type:b, format:gl.RGBA, minFilter:gl.LINEAR_MIPMAP_LINEAR, magFilter:gl.LINEAR})), a.copyTo(this._temp_pot2_texture), a = this._temp_pot2_texture, a.bind(0), gl.generateMipmap(GL.TEXTURE_2D), this._uniforms.u_mipmap_offset = 9); - var c = A._shader, f = this._uniforms; - f.u_mipmap_offset = this.properties.mipmap_offset; - gl.disable(gl.DEPTH_TEST); - gl.disable(gl.BLEND); - this._temp_texture.drawTo(function() { - a.toViewport(c, f); - }); - if (this.isOutputConnected(1) || this.isOutputConnected(2)) { - if (d = this._temp_texture.getPixels()) { - var e = this._luminance; - b = this._temp_texture.type; - e.set(d); - b == gl.UNSIGNED_BYTE && vec4.scale(e, e, 1 / 255); - } - } - } - }, A.pixel_shader = "precision highp float;\n\t\tprecision highp float;\n\t\tuniform mat4 u_samples_a;\n\t\tuniform mat4 u_samples_b;\n\t\tuniform sampler2D u_texture;\n\t\tuniform float u_mipmap_offset;\n\t\tvarying vec2 v_coord;\n\t\t\n\t\tvoid main() {\n\t\t\tvec4 color = vec4(0.0);\n\t\t\t//random average\n\t\t\tfor(int i = 0; i < 4; ++i)\n\t\t\t\tfor(int j = 0; j < 4; ++j)\n\t\t\t\t{\n\t\t\t\t\tcolor += texture2D(u_texture, vec2( u_samples_a[i][j], u_samples_b[i][j] ), u_mipmap_offset );\n\t\t\t\t\tcolor += texture2D(u_texture, vec2( 1.0 - u_samples_a[i][j], 1.0 - u_samples_b[i][j] ), u_mipmap_offset );\n\t\t\t\t}\n\t\t gl_FragColor = color * 0.03125;\n\t\t}\n\t\t", - F.registerNodeType("texture/average", A), e.title = "Smooth", e.desc = "Smooth texture over time", e.prototype.onExecute = function() { - var a = this.getInputData(0); - if (a && this.isOutputConnected(0)) { - e._shader || (e._shader = new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER, e.pixel_shader)); - var b = this._temp_texture; - b && b.type == a.type && b.width == a.width && b.height == a.height || (b = {type:a.type, format:gl.RGBA, filter:gl.NEAREST}, this._temp_texture = new GL.Texture(a.width, a.height, b), this._temp_texture2 = new GL.Texture(a.width, a.height, b), a.copyTo(this._temp_texture2)); - b = this._temp_texture; - var d = this._temp_texture2, c = e._shader, f = this._uniforms; - f.u_factor = 1.0 - this.getInputOrProperty("factor"); - gl.disable(gl.BLEND); - gl.disable(gl.DEPTH_TEST); - b.drawTo(function() { - d.bind(1); - a.toViewport(c, f); - }); - this.setOutputData(0, b); - this._temp_texture = d; - this._temp_texture2 = b; - } - }, e.pixel_shader = "precision highp float;\n\t\tprecision highp float;\n\t\tuniform sampler2D u_texture;\n\t\tuniform sampler2D u_textureB;\n\t\tuniform float u_factor;\n\t\tvarying vec2 v_coord;\n\t\t\n\t\tvoid main() {\n\t\t\tgl_FragColor = mix( texture2D( u_texture, v_coord ), texture2D( u_textureB, v_coord ), u_factor );\n\t\t}\n\t\t", F.registerNodeType("texture/temporal_smooth", e), y.title = "Lineal Avg Smooth", y.desc = "Smooth texture linearly over time", y["@samples"] = {type:"number", - min:1, max:64, step:1, precision:1}, y.prototype.getPreviewTexture = function() { - return this._temp_texture2; - }, y.prototype.onExecute = function() { - var a = this.getInputData(0); - if (a && this.isOutputConnected(0)) { - y._shader || (y._shader_copy = new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER, y.pixel_shader_copy), y._shader_avg = new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER, y.pixel_shader_avg)); - var b = Math.clamp(this.properties.samples, 0, 64), d = this.frame, c = this.properties.frames_interval; - if (0 == c || 0 == d % c) { - d = this._temp_texture; - d && d.type == a.type && d.width == b || (d = {type:a.type, format:gl.RGBA, filter:gl.NEAREST}, this._temp_texture = new GL.Texture(b, 1, d), this._temp_texture2 = new GL.Texture(b, 1, d), this._temp_texture_out = new GL.Texture(1, 1, d)); - var f = this._temp_texture, e = this._temp_texture2, g = y._shader_copy, l = y._shader_avg, p = this._uniforms; - p.u_samples = b; - p.u_isamples = 1.0 / b; - gl.disable(gl.BLEND); - gl.disable(gl.DEPTH_TEST); - f.drawTo(function() { - e.bind(1); - a.toViewport(g, p); - }); - this._temp_texture_out.drawTo(function() { - f.toViewport(l, p); - }); - this.setOutputData(0, this._temp_texture_out); - this._temp_texture = e; - this._temp_texture2 = f; - } else { - this.setOutputData(0, this._temp_texture_out); - } - this.setOutputData(1, this._temp_texture2); - this.frame++; - } - }, y.pixel_shader_copy = "precision highp float;\n\t\tprecision highp float;\n\t\tuniform sampler2D u_texture;\n\t\tuniform sampler2D u_textureB;\n\t\tuniform float u_isamples;\n\t\tvarying vec2 v_coord;\n\t\t\n\t\tvoid main() {\n\t\t\tif( v_coord.x <= u_isamples )\n\t\t\t\tgl_FragColor = texture2D( u_texture, vec2(0.5) );\n\t\t\telse\n\t\t\t\tgl_FragColor = texture2D( u_textureB, v_coord - vec2(u_isamples,0.0) );\n\t\t}\n\t\t", y.pixel_shader_avg = "precision highp float;\n\t\tprecision highp float;\n\t\tuniform sampler2D u_texture;\n\t\tuniform int u_samples;\n\t\tuniform float u_isamples;\n\t\tvarying vec2 v_coord;\n\t\t\n\t\tvoid main() {\n\t\t\tvec4 color = vec4(0.0);\n\t\t\tfor(int i = 0; i < 64; ++i)\n\t\t\t{\n\t\t\t\tcolor += texture2D( u_texture, vec2( float(i)*u_isamples,0.0) );\n\t\t\t\tif(i == (u_samples - 1))\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\tgl_FragColor = color * u_isamples;\n\t\t}\n\t\t", - F.registerNodeType("texture/linear_avg_smooth", y), w.title = "Image to Texture", w.desc = "Uploads an image to the GPU", w.prototype.onExecute = function() { - var a = this.getInputData(0); - if (a) { - var b = a.videoWidth || a.width, d = a.videoHeight || a.height; - if (a.gltexture) { - this.setOutputData(0, a.gltexture); - } else { - var c = this._temp_texture; - c && c.width == b && c.height == d || (this._temp_texture = new GL.Texture(b, d, {format:gl.RGBA, filter:gl.LINEAR})); - try { - this._temp_texture.uploadImage(a); - } catch (S) { - console.error("image comes from an unsafe location, cannot be uploaded to webgl: " + S); - return; - } - this.setOutputData(0, this._temp_texture); - } - } - }, F.registerNodeType("texture/imageToTexture", w), z.widgets_info = {texture:{widget:"texture"}, precision:{widget:"combo", values:c.MODE_VALUES}}, z.title = "LUT", z.desc = "Apply LUT to Texture", z.prototype.onExecute = function() { - if (this.isOutputConnected(0)) { - var a = this.getInputData(0); - if (this.properties.precision === c.PASS_THROUGH || !1 === this.properties.enabled) { - this.setOutputData(0, a); - } else { - if (a) { - var b = this.getInputData(1); - b || (b = c.getTexture(this.properties.texture)); - if (b) { - b.bind(0); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); - gl.bindTexture(gl.TEXTURE_2D, null); - var d = this.properties.intensity; - this.isInputConnected(2) && (this.properties.intensity = d = this.getInputData(2)); - this._tex = c.getTargetTexture(a, this._tex, this.properties.precision); - this._tex.drawTo(function() { - b.bind(1); - a.toViewport(z._shader, {u_texture:0, u_textureB:1, u_amount:d}); - }); - this.setOutputData(0, this._tex); - } else { - this.setOutputData(0, a); - } - } - } - } - }, z.pixel_shader = "precision highp float;\n\t\tprecision highp float;\n\t\tvarying vec2 v_coord;\n\t\tuniform sampler2D u_texture;\n\t\tuniform sampler2D u_textureB;\n\t\tuniform float u_amount;\n\t\t\n\t\tvoid main() {\n\t\t\t lowp vec4 textureColor = clamp( texture2D(u_texture, v_coord), vec4(0.0), vec4(1.0) );\n\t\t\t mediump float blueColor = textureColor.b * 63.0;\n\t\t\t mediump vec2 quad1;\n\t\t\t quad1.y = floor(floor(blueColor) / 8.0);\n\t\t\t quad1.x = floor(blueColor) - (quad1.y * 8.0);\n\t\t\t mediump vec2 quad2;\n\t\t\t quad2.y = floor(ceil(blueColor) / 8.0);\n\t\t\t quad2.x = ceil(blueColor) - (quad2.y * 8.0);\n\t\t\t highp vec2 texPos1;\n\t\t\t texPos1.x = (quad1.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r);\n\t\t\t texPos1.y = 1.0 - ((quad1.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.g));\n\t\t\t highp vec2 texPos2;\n\t\t\t texPos2.x = (quad2.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r);\n\t\t\t texPos2.y = 1.0 - ((quad2.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.g));\n\t\t\t lowp vec4 newColor1 = texture2D(u_textureB, texPos1);\n\t\t\t lowp vec4 newColor2 = texture2D(u_textureB, texPos2);\n\t\t\t lowp vec4 newColor = mix(newColor1, newColor2, fract(blueColor));\n\t\t\t gl_FragColor = vec4( mix( textureColor.rgb, newColor.rgb, u_amount), textureColor.w);\n\t\t}\n\t\t", - F.registerNodeType("texture/LUT", z), l.title = "Texture to Channels", l.desc = "Split texture channels", l.prototype.onExecute = function() { - var a = this.getInputData(0); - if (a) { - this._channels || (this._channels = Array(4)); - for (var b = gl.RGB, d = 0, c = 0; 4 > c; c++) { - this.isOutputConnected(c) ? (this._channels[c] && this._channels[c].width == a.width && this._channels[c].height == a.height && this._channels[c].type == a.type && this._channels[c].format == b || (this._channels[c] = new GL.Texture(a.width, a.height, {type:a.type, format:b, filter:gl.LINEAR})), d++) : this._channels[c] = null; - } - if (d) { - gl.disable(gl.BLEND); - gl.disable(gl.DEPTH_TEST); - var f = Mesh.getScreenQuad(), e = l._shader, g = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]; - for (c = 0; 4 > c; c++) { - this._channels[c] && (this._channels[c].drawTo(function() { - a.bind(0); - e.uniforms({u_texture:0, u_mask:g[c]}).draw(f); - }), this.setOutputData(c, this._channels[c])); - } - } - } - }, l.pixel_shader = "precision highp float;\n\t\tprecision highp float;\n\t\tvarying vec2 v_coord;\n\t\tuniform sampler2D u_texture;\n\t\tuniform vec4 u_mask;\n\t\t\n\t\tvoid main() {\n\t\t gl_FragColor = vec4( vec3( length( texture2D(u_texture, v_coord) * u_mask )), 1.0 );\n\t\t}\n\t\t", F.registerNodeType("texture/textureChannels", l), p.title = "Channels to Texture", p.desc = "Split texture channels", p.widgets_info = {precision:{widget:"combo", values:c.MODE_VALUES}}, p.prototype.onExecute = - function() { - var a = c.getWhiteTexture(), b = this.getInputData(0) || a, d = this.getInputData(1) || a, f = this.getInputData(2) || a, e = this.getInputData(3) || a; - gl.disable(gl.BLEND); - gl.disable(gl.DEPTH_TEST); - var g = Mesh.getScreenQuad(); - p._shader || (p._shader = new GL.Shader(Shader.SCREEN_VERTEX_SHADER, p.pixel_shader)); - var l = p._shader; - a = Math.max(b.width, d.width, f.width, e.width); - var h = Math.max(b.height, d.height, f.height, e.height), m = this.properties.precision == c.HIGH ? c.HIGH_PRECISION_FORMAT : gl.UNSIGNED_BYTE; - this._texture && this._texture.width == a && this._texture.height == h && this._texture.type == m || (this._texture = new GL.Texture(a, h, {type:m, format:gl.RGBA, filter:gl.LINEAR})); - a = this._color; - a[0] = this.properties.R; - a[1] = this.properties.G; - a[2] = this.properties.B; - a[3] = this.properties.A; - var k = this._uniforms; - this._texture.drawTo(function() { - b.bind(0); - d.bind(1); - f.bind(2); - e.bind(3); - l.uniforms(k).draw(g); - }); - this.setOutputData(0, this._texture); - }, p.pixel_shader = "precision highp float;\n\t\tprecision highp float;\n\t\tvarying vec2 v_coord;\n\t\tuniform sampler2D u_textureR;\n\t\tuniform sampler2D u_textureG;\n\t\tuniform sampler2D u_textureB;\n\t\tuniform sampler2D u_textureA;\n\t\tuniform vec4 u_color;\n\t\t\n\t\tvoid main() {\n\t\t gl_FragColor = u_color * vec4( \t\t\t\t\ttexture2D(u_textureR, v_coord).r,\t\t\t\t\ttexture2D(u_textureG, v_coord).r,\t\t\t\t\ttexture2D(u_textureB, v_coord).r,\t\t\t\t\ttexture2D(u_textureA, v_coord).r);\n\t\t}\n\t\t", - F.registerNodeType("texture/channelsTexture", p), r.title = "Color", r.desc = "Generates a 1x1 texture with a constant color", r.widgets_info = {precision:{widget:"combo", values:c.MODE_VALUES}}, r.prototype.onDrawBackground = function(a) { - var b = this.properties.color; - a.fillStyle = "rgb(" + Math.floor(255 * Math.clamp(b[0], 0, 1)) + "," + Math.floor(255 * Math.clamp(b[1], 0, 1)) + "," + Math.floor(255 * Math.clamp(b[2], 0, 1)) + ")"; - this.flags.collapsed ? this.boxcolor = a.fillStyle : a.fillRect(0, 0, this.size[0], this.size[1]); - }, r.prototype.onExecute = function() { - var a = this.properties.precision == c.HIGH ? c.HIGH_PRECISION_FORMAT : gl.UNSIGNED_BYTE; - this._tex && this._tex.type == a || (this._tex = new GL.Texture(1, 1, {format:gl.RGBA, type:a, minFilter:gl.NEAREST})); - a = this.properties.color; - if (this.inputs) { - for (var b = 0; b < this.inputs.length; b++) { - var d = this.inputs[b], f = this.getInputData(b); - if (void 0 !== f) { - switch(d.name) { - case "RGB": - case "RGBA": - a.set(f); - break; - case "R": - a[0] = f; - break; - case "G": - a[1] = f; - break; - case "B": - a[2] = f; - break; - case "A": - a[3] = f; - } - } - } - } - 0.001 < vec4.sqrDist(this._tex_color, a) && (this._tex_color.set(a), this._tex.fill(a)); - this.setOutputData(0, this._tex); - }, r.prototype.onGetInputs = function() { - return [["RGB", "vec3"], ["RGBA", "vec4"], ["R", "number"], ["G", "number"], ["B", "number"], ["A", "number"]]; - }, F.registerNodeType("texture/color", r), f.title = "Gradient", f.desc = "Generates a gradient", f["@A"] = {type:"color"}, f["@B"] = {type:"color"}, f["@texture_size"] = {type:"enum", values:[32, 64, 128, 256, 512]}, f.prototype.onExecute = function() { - gl.disable(gl.BLEND); - gl.disable(gl.DEPTH_TEST); - var a = GL.Mesh.getScreenQuad(), b = f._shader, d = this.getInputData(0); - d || (d = this.properties.A); - var c = this.getInputData(1); - c || (c = this.properties.B); - for (var e = 2; e < this.inputs.length; e++) { - var g = this.inputs[e], l = this.getInputData(e); - void 0 !== l && (this.properties[g.name] = l); - } - var p = this._uniforms; - this._uniforms.u_angle = this.properties.angle * DEG2RAD; - this._uniforms.u_scale = this.properties.scale; - vec3.copy(p.u_colorA, d); - vec3.copy(p.u_colorB, c); - d = parseInt(this.properties.texture_size); - this._tex && this._tex.width == d || (this._tex = new GL.Texture(d, d, {format:gl.RGB, filter:gl.LINEAR})); - this._tex.drawTo(function() { - b.uniforms(p).draw(a); - }); - this.setOutputData(0, this._tex); - }, f.prototype.onGetInputs = function() { - return [["angle", "number"], ["scale", "number"]]; - }, f.pixel_shader = "precision highp float;\n\t\tprecision highp float;\n\t\tvarying vec2 v_coord;\n\t\tuniform float u_angle;\n\t\tuniform float u_scale;\n\t\tuniform vec3 u_colorA;\n\t\tuniform vec3 u_colorB;\n\t\t\n\t\tvec2 rotate(vec2 v, float angle)\n\t\t{\n\t\t\tvec2 result;\n\t\t\tfloat _cos = cos(angle);\n\t\t\tfloat _sin = sin(angle);\n\t\t\tresult.x = v.x * _cos - v.y * _sin;\n\t\t\tresult.y = v.x * _sin + v.y * _cos;\n\t\t\treturn result;\n\t\t}\n\t\tvoid main() {\n\t\t\tfloat f = (rotate(u_scale * (v_coord - vec2(0.5)), u_angle) + vec2(0.5)).x;\n\t\t\tvec3 color = mix(u_colorA,u_colorB,clamp(f,0.0,1.0));\n\t\t gl_FragColor = vec4(color,1.0);\n\t\t}\n\t\t", - F.registerNodeType("texture/gradient", f), a.title = "Mix", a.desc = "Generates a texture mixing two textures", a.widgets_info = {precision:{widget:"combo", values:c.MODE_VALUES}}, a.prototype.onExecute = function() { - var b = this.getInputData(0); - if (this.isOutputConnected(0)) { - if (this.properties.precision === c.PASS_THROUGH) { - this.setOutputData(0, b); - } else { - var d = this.getInputData(1); - if (b && d) { - var f = this.getInputData(2), e = this.getInputData(3); - this._tex = c.getTargetTexture(this.properties.size_from_biggest && d.width > b.width ? d : b, this._tex, this.properties.precision); - gl.disable(gl.BLEND); - gl.disable(gl.DEPTH_TEST); - var g = Mesh.getScreenQuad(), l = null, p = this._uniforms; - f ? (l = a._shader_tex, l || (l = a._shader_tex = new GL.Shader(Shader.SCREEN_VERTEX_SHADER, a.pixel_shader, {MIX_TEX:""}))) : (l = a._shader_factor, l || (l = a._shader_factor = new GL.Shader(Shader.SCREEN_VERTEX_SHADER, a.pixel_shader)), e = null == e ? this.properties.factor : e, p.u_mix.set([e, e, e, e])); - var h = this.properties.invert; - this._tex.drawTo(function() { - b.bind(h ? 1 : 0); - d.bind(h ? 0 : 1); - f && f.bind(2); - l.uniforms(p).draw(g); - }); - this.setOutputData(0, this._tex); - } - } - } - }, a.prototype.onGetInputs = function() { - return [["factor", "number"]]; - }, a.pixel_shader = "precision highp float;\n\t\tprecision highp float;\n\t\tvarying vec2 v_coord;\n\t\tuniform sampler2D u_textureA;\n\t\tuniform sampler2D u_textureB;\n\t\t#ifdef MIX_TEX\n\t\t\tuniform sampler2D u_textureMix;\n\t\t#else\n\t\t\tuniform vec4 u_mix;\n\t\t#endif\n\t\t\n\t\tvoid main() {\n\t\t\t#ifdef MIX_TEX\n\t\t\t vec4 f = texture2D(u_textureMix, v_coord);\n\t\t\t#else\n\t\t\t vec4 f = u_mix;\n\t\t\t#endif\n\t\t gl_FragColor = mix( texture2D(u_textureA, v_coord), texture2D(u_textureB, v_coord), f );\n\t\t}\n\t\t", - F.registerNodeType("texture/mix", a), b.title = "Edges", b.desc = "Detects edges", b.widgets_info = {precision:{widget:"combo", values:c.MODE_VALUES}}, b.prototype.onExecute = function() { - if (this.isOutputConnected(0)) { - var a = this.getInputData(0); - if (this.properties.precision === c.PASS_THROUGH) { - this.setOutputData(0, a); - } else { - if (a) { - this._tex = c.getTargetTexture(a, this._tex, this.properties.precision); - gl.disable(gl.BLEND); - gl.disable(gl.DEPTH_TEST); - var d = Mesh.getScreenQuad(), f = b._shader, e = this.properties.invert, g = this.properties.factor, l = this.properties.threshold ? 1 : 0; - this._tex.drawTo(function() { - a.bind(0); - f.uniforms({u_texture:0, u_isize:[1 / a.width, 1 / a.height], u_factor:g, u_threshold:l, u_invert:e ? 1 : 0}).draw(d); - }); - this.setOutputData(0, this._tex); - } - } - } - }, b.pixel_shader = "precision highp float;\n\t\tprecision highp float;\n\t\tvarying vec2 v_coord;\n\t\tuniform sampler2D u_texture;\n\t\tuniform vec2 u_isize;\n\t\tuniform int u_invert;\n\t\tuniform float u_factor;\n\t\tuniform float u_threshold;\n\t\t\n\t\tvoid main() {\n\t\t\tvec4 center = texture2D(u_texture, v_coord);\n\t\t\tvec4 up = texture2D(u_texture, v_coord + u_isize * vec2(0.0,1.0) );\n\t\t\tvec4 down = texture2D(u_texture, v_coord + u_isize * vec2(0.0,-1.0) );\n\t\t\tvec4 left = texture2D(u_texture, v_coord + u_isize * vec2(1.0,0.0) );\n\t\t\tvec4 right = texture2D(u_texture, v_coord + u_isize * vec2(-1.0,0.0) );\n\t\t\tvec4 diff = abs(center - up) + abs(center - down) + abs(center - left) + abs(center - right);\n\t\t\tdiff *= u_factor;\n\t\t\tif(u_invert == 1)\n\t\t\t\tdiff.xyz = vec3(1.0) - diff.xyz;\n\t\t\tif( u_threshold == 0.0 )\n\t\t\t\tgl_FragColor = vec4( diff.xyz, center.a );\n\t\t\telse\n\t\t\t\tgl_FragColor = vec4( diff.x > 0.5 ? 1.0 : 0.0, diff.y > 0.5 ? 1.0 : 0.0, diff.z > 0.5 ? 1.0 : 0.0, center.a );\n\t\t}\n\t\t", - F.registerNodeType("texture/edges", b), d.title = "Depth Range", d.desc = "Generates a texture with a depth range", d.prototype.onExecute = function() { - if (this.isOutputConnected(0)) { - var a = this.getInputData(0); - if (a) { - var b = gl.UNSIGNED_BYTE; - this.properties.high_precision && (b = gl.half_float_ext ? gl.HALF_FLOAT_OES : gl.FLOAT); - this._temp_texture && this._temp_texture.type == b && this._temp_texture.width == a.width && this._temp_texture.height == a.height || (this._temp_texture = new GL.Texture(a.width, a.height, {type:b, format:gl.RGBA, filter:gl.LINEAR})); - var c = this._uniforms; - b = this.properties.distance; - this.isInputConnected(1) && (b = this.getInputData(1), this.properties.distance = b); - var f = this.properties.range; - this.isInputConnected(2) && (f = this.getInputData(2), this.properties.range = f); - c.u_distance = b; - c.u_range = f; - gl.disable(gl.BLEND); - gl.disable(gl.DEPTH_TEST); - var e = Mesh.getScreenQuad(); - d._shader || (d._shader = new GL.Shader(Shader.SCREEN_VERTEX_SHADER, d.pixel_shader), d._shader_onlydepth = new GL.Shader(Shader.SCREEN_VERTEX_SHADER, d.pixel_shader, {ONLY_DEPTH:""})); - var g = this.properties.only_depth ? d._shader_onlydepth : d._shader; - b = null; - b = a.near_far_planes ? a.near_far_planes : window.LS && LS.Renderer._main_camera ? LS.Renderer._main_camera._uniforms.u_camera_planes : [0.1, 1000]; - c.u_camera_planes = b; - this._temp_texture.drawTo(function() { - a.bind(0); - g.uniforms(c).draw(e); - }); - this._temp_texture.near_far_planes = b; - this.setOutputData(0, this._temp_texture); - } - } - }, d.pixel_shader = "precision highp float;\n\t\tprecision highp float;\n\t\tvarying vec2 v_coord;\n\t\tuniform sampler2D u_texture;\n\t\tuniform vec2 u_camera_planes;\n\t\tuniform float u_distance;\n\t\tuniform float u_range;\n\t\t\n\t\tfloat LinearDepth()\n\t\t{\n\t\t\tfloat zNear = u_camera_planes.x;\n\t\t\tfloat zFar = u_camera_planes.y;\n\t\t\tfloat depth = texture2D(u_texture, v_coord).x;\n\t\t\tdepth = depth * 2.0 - 1.0;\n\t\t\treturn zNear * (depth + 1.0) / (zFar + zNear - depth * (zFar - zNear));\n\t\t}\n\t\t\n\t\tvoid main() {\n\t\t\tfloat depth = LinearDepth();\n\t\t\t#ifdef ONLY_DEPTH\n\t\t\t gl_FragColor = vec4(depth);\n\t\t\t#else\n\t\t\t\tfloat diff = abs(depth * u_camera_planes.y - u_distance);\n\t\t\t\tfloat dof = 1.0;\n\t\t\t\tif(diff <= u_range)\n\t\t\t\t\tdof = diff / u_range;\n\t\t\t gl_FragColor = vec4(dof);\n\t\t\t#endif\n\t\t}\n\t\t", - F.registerNodeType("texture/depth_range", d), q.widgets_info = {precision:{widget:"combo", values:c.MODE_VALUES}}, q.title = "Linear Depth", q.desc = "Creates a color texture with linear depth", q.prototype.onExecute = function() { - if (this.isOutputConnected(0)) { - var a = this.getInputData(0); - if (a && (a.format == gl.DEPTH_COMPONENT || a.format == gl.DEPTH_STENCIL)) { - var b = this.properties.precision == c.HIGH ? gl.HIGH_PRECISION_FORMAT : gl.UNSIGNED_BYTE; - this._temp_texture && this._temp_texture.type == b && this._temp_texture.width == a.width && this._temp_texture.height == a.height || (this._temp_texture = new GL.Texture(a.width, a.height, {type:b, format:gl.RGB, filter:gl.LINEAR})); - var d = this._uniforms; - d.u_near = a.near_far_planes[0]; - d.u_far = a.near_far_planes[1]; - d.u_invert = this.properties.invert ? 1 : 0; - gl.disable(gl.BLEND); - gl.disable(gl.DEPTH_TEST); - var f = Mesh.getScreenQuad(); - q._shader || (q._shader = new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER, q.pixel_shader)); - var e = q._shader; - b = null; - b = a.near_far_planes ? a.near_far_planes : window.LS && LS.Renderer._main_camera ? LS.Renderer._main_camera._uniforms.u_camera_planes : [0.1, 1000]; - d.u_camera_planes = b; - this._temp_texture.drawTo(function() { - a.bind(0); - e.uniforms(d).draw(f); - }); - this._temp_texture.near_far_planes = b; - this.setOutputData(0, this._temp_texture); - } - } - }, q.pixel_shader = "precision highp float;\n\t\tprecision highp float;\n\t\tvarying vec2 v_coord;\n\t\tuniform sampler2D u_texture;\n\t\tuniform float u_near;\n\t\tuniform float u_far;\n\t\tuniform int u_invert;\n\t\t\n\t\tvoid main() {\n\t\t\tfloat zNear = u_near;\n\t\t\tfloat zFar = u_far;\n\t\t\tfloat depth = texture2D(u_texture, v_coord).x;\n\t\t\tdepth = depth * 2.0 - 1.0;\n\t\t\tfloat f = zNear * (depth + 1.0) / (zFar + zNear - depth * (zFar - zNear));\n\t\t\tif( u_invert == 1 )\n\t\t\t\tf = 1.0 - f;\n\t\t\tgl_FragColor = vec4(vec3(f),1.0);\n\t\t}\n\t\t", - F.registerNodeType("texture/linear_depth", q), n.title = "Blur", n.desc = "Blur a texture", n.widgets_info = {precision:{widget:"combo", values:c.MODE_VALUES}}, n.max_iterations = 20, n.prototype.onExecute = function() { - var a = this.getInputData(0); - if (a && this.isOutputConnected(0)) { - var b = this._final_texture; - b && b.width == a.width && b.height == a.height && b.type == a.type || (b = this._final_texture = new GL.Texture(a.width, a.height, {type:a.type, format:gl.RGBA, filter:gl.LINEAR})); - var d = this.properties.iterations; - this.isInputConnected(1) && (d = this.getInputData(1), this.properties.iterations = d); - d = Math.min(Math.floor(d), n.max_iterations); - if (0 == d) { - this.setOutputData(0, a); - } else { - var c = this.properties.intensity; - this.isInputConnected(2) && (c = this.getInputData(2), this.properties.intensity = c); - var f = F.camera_aspect; - f || void 0 === window.gl || (f = gl.canvas.height / gl.canvas.width); - f || (f = 1); - f = this.properties.preserve_aspect ? f : 1; - var e = this.properties.scale || [1, 1]; - a.applyBlur(f * e[0], e[1], c, b); - for (a = 1; a < d; ++a) { - b.applyBlur(f * e[0] * (a + 1), e[1] * (a + 1), c); - } - this.setOutputData(0, b); - } - } - }, F.registerNodeType("texture/blur", n), H.title = "Glow", H.desc = "Filters a texture giving it a glow effect", H.weights = new Float32Array([0.5, 0.4, 0.3, 0.2]), H.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:c.MODE_VALUES}}, H.prototype.onGetInputs = function() { - return [["enabled", "boolean"], ["threshold", "number"], ["intensity", "number"], ["persistence", "number"], ["iterations", "number"], ["dirt_factor", "number"]]; - }, H.prototype.onGetOutputs = function() { - return [["average", "Texture"]]; - }, H.prototype.onExecute = function() { - var a = this.getInputData(0); - if (a && this.isAnyOutputConnected()) { - if (this.properties.precision === c.PASS_THROUGH || !1 === this.getInputOrProperty("enabled")) { - this.setOutputData(0, a); - } else { - var b = a.width, d = a.height, f = {format:a.format, type:a.type, minFilter:GL.LINEAR, magFilter:GL.LINEAR, wrap:gl.CLAMP_TO_EDGE}, e = c.getTextureType(this.properties.precision, a), g = this._uniforms, l = this._textures, p = H._cut_shader; - p || (p = H._cut_shader = new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER, H.cut_pixel_shader)); - gl.disable(gl.DEPTH_TEST); - gl.disable(gl.BLEND); - g.u_threshold = this.getInputOrProperty("threshold"); - var h = l[0] = GL.Texture.getTemporary(b, d, f); - a.blit(h, p.uniforms(g)); - var m = h, k = this.getInputOrProperty("iterations"); - k = Math.clamp(k, 1, 16) | 0; - var n = g.u_texel_size, r = this.getInputOrProperty("intensity"); - g.u_intensity = 1; - g.u_delta = this.properties.scale; - p = H._shader; - p || (p = H._shader = new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER, H.scale_pixel_shader)); - for (var q = 1; q < k; q++) { - b >>= 1; - 1 < (d | 0) && (d >>= 1); - if (2 > b) { - break; - } - h = l[q] = GL.Texture.getTemporary(b, d, f); - n[0] = 1 / m.width; - n[1] = 1 / m.height; - m.blit(h, p.uniforms(g)); - m = h; - } - this.isOutputConnected(2) && (b = this._average_texture, b && b.type == a.type && b.format == a.format || (b = this._average_texture = new GL.Texture(1, 1, {type:a.type, format:a.format, filter:gl.LINEAR})), n[0] = 1 / m.width, n[1] = 1 / m.height, g.u_intensity = r, g.u_delta = 1, m.blit(b, p.uniforms(g)), this.setOutputData(2, b)); - gl.enable(gl.BLEND); - gl.blendFunc(gl.ONE, gl.ONE); - g.u_intensity = this.getInputOrProperty("persistence"); - g.u_delta = 0.5; - for (q -= 2; 0 <= q; q--) { - h = l[q], l[q] = null, n[0] = 1 / m.width, n[1] = 1 / m.height, m.blit(h, p.uniforms(g)), GL.Texture.releaseTemporary(m), m = h; - } - gl.disable(gl.BLEND); - this.isOutputConnected(1) && (l = this._glow_texture, l && l.width == a.width && l.height == a.height && l.type == e && l.format == a.format || (l = this._glow_texture = new GL.Texture(a.width, a.height, {type:e, format:a.format, filter:gl.LINEAR})), m.blit(l), this.setOutputData(1, l)); - if (this.isOutputConnected(0)) { - l = this._final_texture; - l && l.width == a.width && l.height == a.height && l.type == e && l.format == a.format || (l = this._final_texture = new GL.Texture(a.width, a.height, {type:e, format:a.format, filter:gl.LINEAR})); - var t = this.getInputData(1), x = this.getInputOrProperty("dirt_factor"); - g.u_intensity = r; - p = t ? H._dirt_final_shader : H._final_shader; - p || (p = t ? H._dirt_final_shader = new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER, H.final_pixel_shader, {USE_DIRT:""}) : H._final_shader = new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER, H.final_pixel_shader)); - l.drawTo(function() { - a.bind(0); - m.bind(1); - t && (p.setUniform("u_dirt_factor", x), p.setUniform("u_dirt_texture", t.bind(2))); - p.toViewport(g); - }); - this.setOutputData(0, l); - } - GL.Texture.releaseTemporary(m); - } - } - }, H.cut_pixel_shader = "precision highp float;\n\tvarying vec2 v_coord;\n\tuniform sampler2D u_texture;\n\tuniform float u_threshold;\n\tvoid main() {\n\t\tgl_FragColor = max( texture2D( u_texture, v_coord ) - vec4( u_threshold ), vec4(0.0) );\n\t}", H.scale_pixel_shader = "precision highp float;\n\tvarying vec2 v_coord;\n\tuniform sampler2D u_texture;\n\tuniform vec2 u_texel_size;\n\tuniform float u_delta;\n\tuniform float u_intensity;\n\t\n\tvec4 sampleBox(vec2 uv) {\n\t\tvec4 o = u_texel_size.xyxy * vec2(-u_delta, u_delta).xxyy;\n\t\tvec4 s = texture2D( u_texture, uv + o.xy ) + texture2D( u_texture, uv + o.zy) + texture2D( u_texture, uv + o.xw) + texture2D( u_texture, uv + o.zw);\n\t\treturn s * 0.25;\n\t}\n\tvoid main() {\n\t\tgl_FragColor = u_intensity * sampleBox( v_coord );\n\t}", - H.final_pixel_shader = "precision highp float;\n\tvarying vec2 v_coord;\n\tuniform sampler2D u_texture;\n\tuniform sampler2D u_glow_texture;\n\t#ifdef USE_DIRT\n\t\tuniform sampler2D u_dirt_texture;\n\t#endif\n\tuniform vec2 u_texel_size;\n\tuniform float u_delta;\n\tuniform float u_intensity;\n\tuniform float u_dirt_factor;\n\t\n\tvec4 sampleBox(vec2 uv) {\n\t\tvec4 o = u_texel_size.xyxy * vec2(-u_delta, u_delta).xxyy;\n\t\tvec4 s = texture2D( u_glow_texture, uv + o.xy ) + texture2D( u_glow_texture, uv + o.zy) + texture2D( u_glow_texture, uv + o.xw) + texture2D( u_glow_texture, uv + o.zw);\n\t\treturn s * 0.25;\n\t}\n\tvoid main() {\n\t\tvec4 glow = sampleBox( v_coord );\n\t\t#ifdef USE_DIRT\n\t\t\tglow = mix( glow, glow * texture2D( u_dirt_texture, v_coord ), u_dirt_factor );\n\t\t#endif\n\t\tgl_FragColor = texture2D( u_texture, v_coord ) + u_intensity * glow;\n\t}", - F.registerNodeType("texture/glow", H), G.title = "Kuwahara Filter", G.desc = "Filters a texture giving an artistic oil canvas painting", G.max_radius = 10, G._shaders = [], G.prototype.onExecute = function() { - var a = this.getInputData(0); - if (a && this.isOutputConnected(0)) { - var b = this._temp_texture; - b && b.width == a.width && b.height == a.height && b.type == a.type || (this._temp_texture = new GL.Texture(a.width, a.height, {type:a.type, format:gl.RGBA, filter:gl.LINEAR})); - b = this.properties.radius; - b = Math.min(Math.floor(b), G.max_radius); - if (0 == b) { - this.setOutputData(0, a); - } else { - var d = this.properties.intensity, c = F.camera_aspect; - c || void 0 === window.gl || (c = gl.canvas.height / gl.canvas.width); - c || (c = 1); - c = this.properties.preserve_aspect ? c : 1; - G._shaders[b] || (G._shaders[b] = new GL.Shader(Shader.SCREEN_VERTEX_SHADER, G.pixel_shader, {RADIUS:b.toFixed(0)})); - var f = G._shaders[b], e = GL.Mesh.getScreenQuad(); - a.bind(0); - this._temp_texture.drawTo(function() { - f.uniforms({u_texture:0, u_intensity:d, u_resolution:[a.width, a.height], u_iResolution:[1 / a.width, 1 / a.height]}).draw(e); - }); - this.setOutputData(0, this._temp_texture); - } - } - }, G.pixel_shader = "\nprecision highp float;\nvarying vec2 v_coord;\nuniform sampler2D u_texture;\nuniform float u_intensity;\nuniform vec2 u_resolution;\nuniform vec2 u_iResolution;\n#ifndef RADIUS\n\t#define RADIUS 7\n#endif\nvoid main() {\n\n\tconst int radius = RADIUS;\n\tvec2 fragCoord = v_coord;\n\tvec2 src_size = u_iResolution;\n\tvec2 uv = v_coord;\n\tfloat n = float((radius + 1) * (radius + 1));\n\tint i;\n\tint j;\n\tvec3 m0 = vec3(0.0); vec3 m1 = vec3(0.0); vec3 m2 = vec3(0.0); vec3 m3 = vec3(0.0);\n\tvec3 s0 = vec3(0.0); vec3 s1 = vec3(0.0); vec3 s2 = vec3(0.0); vec3 s3 = vec3(0.0);\n\tvec3 c;\n\t\n\tfor (int j = -radius; j <= 0; ++j) {\n\t\tfor (int i = -radius; i <= 0; ++i) {\n\t\t\tc = texture2D(u_texture, uv + vec2(i,j) * src_size).rgb;\n\t\t\tm0 += c;\n\t\t\ts0 += c * c;\n\t\t}\n\t}\n\t\n\tfor (int j = -radius; j <= 0; ++j) {\n\t\tfor (int i = 0; i <= radius; ++i) {\n\t\t\tc = texture2D(u_texture, uv + vec2(i,j) * src_size).rgb;\n\t\t\tm1 += c;\n\t\t\ts1 += c * c;\n\t\t}\n\t}\n\t\n\tfor (int j = 0; j <= radius; ++j) {\n\t\tfor (int i = 0; i <= radius; ++i) {\n\t\t\tc = texture2D(u_texture, uv + vec2(i,j) * src_size).rgb;\n\t\t\tm2 += c;\n\t\t\ts2 += c * c;\n\t\t}\n\t}\n\t\n\tfor (int j = 0; j <= radius; ++j) {\n\t\tfor (int i = -radius; i <= 0; ++i) {\n\t\t\tc = texture2D(u_texture, uv + vec2(i,j) * src_size).rgb;\n\t\t\tm3 += c;\n\t\t\ts3 += c * c;\n\t\t}\n\t}\n\t\n\tfloat min_sigma2 = 1e+2;\n\tm0 /= n;\n\ts0 = abs(s0 / n - m0 * m0);\n\t\n\tfloat sigma2 = s0.r + s0.g + s0.b;\n\tif (sigma2 < min_sigma2) {\n\t\tmin_sigma2 = sigma2;\n\t\tgl_FragColor = vec4(m0, 1.0);\n\t}\n\t\n\tm1 /= n;\n\ts1 = abs(s1 / n - m1 * m1);\n\t\n\tsigma2 = s1.r + s1.g + s1.b;\n\tif (sigma2 < min_sigma2) {\n\t\tmin_sigma2 = sigma2;\n\t\tgl_FragColor = vec4(m1, 1.0);\n\t}\n\t\n\tm2 /= n;\n\ts2 = abs(s2 / n - m2 * m2);\n\t\n\tsigma2 = s2.r + s2.g + s2.b;\n\tif (sigma2 < min_sigma2) {\n\t\tmin_sigma2 = sigma2;\n\t\tgl_FragColor = vec4(m2, 1.0);\n\t}\n\t\n\tm3 /= n;\n\ts3 = abs(s3 / n - m3 * m3);\n\t\n\tsigma2 = s3.r + s3.g + s3.b;\n\tif (sigma2 < min_sigma2) {\n\t\tmin_sigma2 = sigma2;\n\t\tgl_FragColor = vec4(m3, 1.0);\n\t}\n}\n", - F.registerNodeType("texture/kuwahara", G), L.title = "XDoG Filter", L.desc = "Filters a texture giving an artistic ink style", L.max_radius = 10, L._shaders = [], L.prototype.onExecute = function() { - var a = this.getInputData(0); - if (a && this.isOutputConnected(0)) { - var b = this._temp_texture; - b && b.width == a.width && b.height == a.height && b.type == a.type || (this._temp_texture = new GL.Texture(a.width, a.height, {type:a.type, format:gl.RGBA, filter:gl.LINEAR})); - L._xdog_shader || (L._xdog_shader = new GL.Shader(Shader.SCREEN_VERTEX_SHADER, L.xdog_pixel_shader)); - var d = L._xdog_shader, c = GL.Mesh.getScreenQuad(), f = this.properties.sigma, e = this.properties.k, g = this.properties.p, l = this.properties.epsilon, p = this.properties.phi; - a.bind(0); - this._temp_texture.drawTo(function() { - d.uniforms({src:0, sigma:f, k:e, p:g, epsilon:l, phi:p, cvsWidth:a.width, cvsHeight:a.height}).draw(c); - }); - this.setOutputData(0, this._temp_texture); - } - }, L.xdog_pixel_shader = "\nprecision highp float;\nuniform sampler2D src;\n\nuniform float cvsHeight;\nuniform float cvsWidth;\n\nuniform float sigma;\nuniform float k;\nuniform float p;\nuniform float epsilon;\nuniform float phi;\nvarying vec2 v_coord;\n\nfloat cosh(float val)\n{\n\tfloat tmp = exp(val);\n\tfloat cosH = (tmp + 1.0 / tmp) / 2.0;\n\treturn cosH;\n}\n\nfloat tanh(float val)\n{\n\tfloat tmp = exp(val);\n\tfloat tanH = (tmp - 1.0 / tmp) / (tmp + 1.0 / tmp);\n\treturn tanH;\n}\n\nfloat sinh(float val)\n{\n\tfloat tmp = exp(val);\n\tfloat sinH = (tmp - 1.0 / tmp) / 2.0;\n\treturn sinH;\n}\n\nvoid main(void){\n\tvec3 destColor = vec3(0.0);\n\tfloat tFrag = 1.0 / cvsHeight;\n\tfloat sFrag = 1.0 / cvsWidth;\n\tvec2 Frag = vec2(sFrag,tFrag);\n\tvec2 uv = gl_FragCoord.st;\n\tfloat twoSigmaESquared = 2.0 * sigma * sigma;\n\tfloat twoSigmaRSquared = twoSigmaESquared * k * k;\n\tint halfWidth = int(ceil( 1.0 * sigma * k ));\n\n\tconst int MAX_NUM_ITERATION = 99999;\n\tvec2 sum = vec2(0.0);\n\tvec2 norm = vec2(0.0);\n\n\tfor(int cnt=0;cnt (2*halfWidth+1)*(2*halfWidth+1)){break;}\n\t\tint i = int(cnt / (2*halfWidth+1)) - halfWidth;\n\t\tint j = cnt - halfWidth - int(cnt / (2*halfWidth+1)) * (2*halfWidth+1);\n\n\t\tfloat d = length(vec2(i,j));\n\t\tvec2 kernel = vec2( exp( -d * d / twoSigmaESquared ), \n\t\t\t\t\t\t\texp( -d * d / twoSigmaRSquared ));\n\n\t\tvec2 L = texture2D(src, (uv + vec2(i,j)) * Frag).xx;\n\n\t\tnorm += kernel;\n\t\tsum += kernel * L;\n\t}\n\n\tsum /= norm;\n\n\tfloat H = 100.0 * ((1.0 + p) * sum.x - p * sum.y);\n\tfloat edge = ( H > epsilon )? 1.0 : 1.0 + tanh( phi * (H - epsilon));\n\tdestColor = vec3(edge);\n\tgl_FragColor = vec4(destColor, 1.0);\n}", - F.registerNodeType("texture/xDoG", L), J.title = "Webcam", J.desc = "Webcam texture", J.is_webcam_open = !1, J.prototype.openStream = function() { - if (navigator.getUserMedia) { - this._waiting_confirmation = !0; - navigator.mediaDevices.getUserMedia({audio:!1, video:{facingMode:this.properties.facingMode}}).then(this.streamReady.bind(this)).catch(function(b) { - J.is_webcam_open = !1; - console.log("Webcam rejected", b); - a._webcam_stream = !1; - a.boxcolor = "red"; - a.trigger("stream_error"); - }); - var a = this; - } - }, J.prototype.closeStream = function() { - if (this._webcam_stream) { - var a = this._webcam_stream.getTracks(); - if (a.length) { - for (var b = 0; b < a.length; ++b) { - a[b].stop(); - } - } - J.is_webcam_open = !1; - this._video = this._webcam_stream = null; - this.boxcolor = "black"; - this.trigger("stream_closed"); - } - }, J.prototype.streamReady = function(a) { - this._webcam_stream = a; - this.boxcolor = "green"; - var b = this._video; - b || (b = document.createElement("video"), b.autoplay = !0, b.srcObject = a, this._video = b, b.onloadedmetadata = function(a) { - J.is_webcam_open = !0; - console.log(a); - }); - this.trigger("stream_ready", b); - }, J.prototype.onPropertyChanged = function(a, b) { - "facingMode" == a && (this.properties.facingMode = b, this.closeStream(), this.openStream()); - }, J.prototype.onRemoved = function() { - if (this._webcam_stream) { - var a = this._webcam_stream.getTracks(); - if (a.length) { - for (var b = 0; b < a.length; ++b) { - a[b].stop(); - } - } - this._video = this._webcam_stream = null; - } - }, J.prototype.onDrawBackground = function(a) { - this.flags.collapsed || 20 >= this.size[1] || !this._video || (a.save(), a.webgl ? this._video_texture && a.drawImage(this._video_texture, 0, 0, this.size[0], this.size[1]) : a.drawImage(this._video, 0, 0, this.size[0], this.size[1]), a.restore()); - }, J.prototype.onExecute = function() { - null != this._webcam_stream || this._waiting_confirmation || this.openStream(); - if (this._video && this._video.videoWidth) { - var a = this._video.videoWidth, b = this._video.videoHeight, d = this._video_texture; - d && d.width == a && d.height == b || (this._video_texture = new GL.Texture(a, b, {format:gl.RGB, filter:gl.LINEAR})); - this._video_texture.uploadImage(this._video); - this._video_texture.version = ++this.version; - this.properties.texture_name && (c.getTexturesContainer()[this.properties.texture_name] = this._video_texture); - this.setOutputData(0, this._video_texture); - for (a = 1; a < this.outputs.length; ++a) { - if (this.outputs[a]) { - switch(this.outputs[a].name) { - case "width": - this.setOutputData(a, this._video.videoWidth); - break; - case "height": - this.setOutputData(a, this._video.videoHeight); - } - } - } - } - }, J.prototype.onGetOutputs = function() { - return [["width", "number"], ["height", "number"], ["stream_ready", F.EVENT], ["stream_closed", F.EVENT], ["stream_error", F.EVENT]]; - }, F.registerNodeType("texture/webcam", J), M.title = "Lens FX", M.desc = "distortion and chromatic aberration", M.widgets_info = {precision:{widget:"combo", values:c.MODE_VALUES}}, M.prototype.onGetInputs = function() { - return [["enabled", "boolean"]]; - }, M.prototype.onExecute = function() { - var a = this.getInputData(0); - if (a && this.isOutputConnected(0)) { - if (this.properties.precision === c.PASS_THROUGH || !1 === this.getInputOrProperty("enabled")) { - this.setOutputData(0, a); - } else { - var b = this._temp_texture; - b && b.width == a.width && b.height == a.height && b.type == a.type || (b = this._temp_texture = new GL.Texture(a.width, a.height, {type:a.type, format:gl.RGBA, filter:gl.LINEAR})); - var d = M._shader; - d || (d = M._shader = new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER, M.pixel_shader)); - var f = this.getInputData(1); - null == f && (f = this.properties.factor); - var e = this._uniforms; - e.u_factor = f; - gl.disable(gl.DEPTH_TEST); - b.drawTo(function() { - a.bind(0); - d.uniforms(e).draw(GL.Mesh.getScreenQuad()); - }); - this.setOutputData(0, b); - } - } - }, M.pixel_shader = "precision highp float;\n\t\tvarying vec2 v_coord;\n\t\tuniform sampler2D u_texture;\n\t\tuniform float u_factor;\n\t\tvec2 barrelDistortion(vec2 coord, float amt) {\n\t\t\tvec2 cc = coord - 0.5;\n\t\t\tfloat dist = dot(cc, cc);\n\t\t\treturn coord + cc * dist * amt;\n\t\t}\n\t\t\n\t\tfloat sat( float t )\n\t\t{\n\t\t\treturn clamp( t, 0.0, 1.0 );\n\t\t}\n\t\t\n\t\tfloat linterp( float t ) {\n\t\t\treturn sat( 1.0 - abs( 2.0*t - 1.0 ) );\n\t\t}\n\t\t\n\t\tfloat remap( float t, float a, float b ) {\n\t\t\treturn sat( (t - a) / (b - a) );\n\t\t}\n\t\t\n\t\tvec4 spectrum_offset( float t ) {\n\t\t\tvec4 ret;\n\t\t\tfloat lo = step(t,0.5);\n\t\t\tfloat hi = 1.0-lo;\n\t\t\tfloat w = linterp( remap( t, 1.0/6.0, 5.0/6.0 ) );\n\t\t\tret = vec4(lo,1.0,hi, 1.) * vec4(1.0-w, w, 1.0-w, 1.);\n\t\t\n\t\t\treturn pow( ret, vec4(1.0/2.2) );\n\t\t}\n\t\t\n\t\tconst float max_distort = 2.2;\n\t\tconst int num_iter = 12;\n\t\tconst float reci_num_iter_f = 1.0 / float(num_iter);\n\t\t\n\t\tvoid main()\n\t\t{\t\n\t\t\tvec2 uv=v_coord;\n\t\t\tvec4 sumcol = vec4(0.0);\n\t\t\tvec4 sumw = vec4(0.0);\t\n\t\t\tfor ( int i=0; i Math.abs(b)) { - return c[1]; - } - a = (a - c[0]) / b; - return c[1] * (1.0 - a) + f[1] * a; - } - } - return 0; - } - }, u.prototype.updateCurve = function() { - for (var a = this._values, b = a.length / 4, d = this.properties.split_channels, c = 0; c < b; ++c) { - if (d) { - a[4 * c] = Math.clamp(255 * this.sampleCurve(c / b, this._points.R), 0, 255), a[4 * c + 1] = Math.clamp(255 * this.sampleCurve(c / b, this._points.G), 0, 255), a[4 * c + 2] = Math.clamp(255 * this.sampleCurve(c / b, this._points.B), 0, 255); - } else { - var f = this.sampleCurve(c / b); - a[4 * c] = a[4 * c + 1] = a[4 * c + 2] = Math.clamp(255 * f, 0, 255); - } - a[4 * c + 3] = 255; - } - this._curve_texture || (this._curve_texture = new GL.Texture(256, 1, {format:gl.RGBA, magFilter:gl.LINEAR, wrap:gl.CLAMP_TO_EDGE})); - this._curve_texture.uploadData(a, null, !0); - }, u.prototype.onSerialize = function(a) { - var b = {}, d; - for (d in this._points) { - b[d] = this._points[d].concat(); - } - a.curves = b; - }, u.prototype.onConfigure = function(a) { - this._points = a.curves; - this.curve_editor && (curve_editor.points = this._points); - this._must_update = !0; - }, u.prototype.onMouseDown = function(a, b, d) { - if (this.curve_editor) { - return (a = this.curve_editor.onMouseDown([b[0], b[1] - this.curve_offset], d)) && this.captureInput(!0), a; - } - }, u.prototype.onMouseMove = function(a, b, d) { - if (this.curve_editor) { - return this.curve_editor.onMouseMove([b[0], b[1] - this.curve_offset], d); - } - }, u.prototype.onMouseUp = function(a, b, d) { - if (this.curve_editor) { - return this.curve_editor.onMouseUp([b[0], b[1] - this.curve_offset], d); - } - this.captureInput(!1); - }, u.channel_line_colors = {RGB:"#666", R:"#F33", G:"#3F3", B:"#33F"}, u.prototype.onDrawBackground = function(a, b) { - if (!this.flags.collapsed) { - this.curve_editor || (this.curve_editor = new F.CurveEditor(this._points.R)); - a.save(); - a.translate(0, this.curve_offset); - var d = this.widgets[1].value; - this.properties.split_channels ? ("RGB" == d && (this.widgets[1].value = d = "R", this.widgets[1].disabled = !1), this.curve_editor.points = this._points.R, this.curve_editor.draw(a, [this.size[0], this.size[1] - this.curve_offset], b, "#111", u.channel_line_colors.R, !0), a.globalCompositeOperation = "lighten", this.curve_editor.points = this._points.G, this.curve_editor.draw(a, [this.size[0], this.size[1] - this.curve_offset], b, null, u.channel_line_colors.G, !0), this.curve_editor.points = - this._points.B, this.curve_editor.draw(a, [this.size[0], this.size[1] - this.curve_offset], b, null, u.channel_line_colors.B, !0), a.globalCompositeOperation = "source-over") : (this.widgets[1].value = d = "RGB", this.widgets[1].disabled = !0); - this.curve_editor.points = this._points[d]; - this.curve_editor.draw(a, [this.size[0], this.size[1] - this.curve_offset], b, this.properties.split_channels ? null : "#111", u.channel_line_colors[d]); - a.restore(); - } - }, u.pixel_shader = "precision highp float;\n\t\tvarying vec2 v_coord;\n\t\tuniform sampler2D u_texture;\n\t\tuniform sampler2D u_curve;\n\t\tuniform float u_range;\n\t\t\n\t\tvoid main() {\n\t\t\tvec4 color = texture2D( u_texture, v_coord ) * u_range;\n\t\t\tcolor.x = texture2D( u_curve, vec2( color.x, 0.5 ) ).x;\n\t\t\tcolor.y = texture2D( u_curve, vec2( color.y, 0.5 ) ).y;\n\t\t\tcolor.z = texture2D( u_curve, vec2( color.z, 0.5 ) ).z;\n\t\t\t//color.w = texture2D( u_curve, vec2( color.w, 0.5 ) ).w;\n\t\t\tgl_FragColor = color;\n\t\t}", - F.registerNodeType("texture/curve", u), C.title = "Exposition", C.desc = "Controls texture exposition", C.widgets_info = {exposition:{widget:"slider", min:0, max:3}, precision:{widget:"combo", values:c.MODE_VALUES}}, C.prototype.onExecute = function() { - var a = this.getInputData(0); - if (a && this.isOutputConnected(0)) { - var b = this._temp_texture; - b && b.width == a.width && b.height == a.height && b.type == a.type || (b = this._temp_texture = new GL.Texture(a.width, a.height, {type:a.type, format:gl.RGBA, filter:gl.LINEAR})); - var d = C._shader; - d || (d = C._shader = new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER, C.pixel_shader)); - var c = this.getInputData(1); - null != c && (this.properties.exposition = c); - var f = this._uniforms; - b.drawTo(function() { - gl.disable(gl.DEPTH_TEST); - a.bind(0); - d.uniforms(f).draw(GL.Mesh.getScreenQuad()); - }); - this.setOutputData(0, b); - } - }, C.pixel_shader = "precision highp float;\n\t\tvarying vec2 v_coord;\n\t\tuniform sampler2D u_texture;\n\t\tuniform float u_exposition;\n\t\t\n\t\tvoid main() {\n\t\t\tvec4 color = texture2D( u_texture, v_coord );\n\t\t\tgl_FragColor = vec4( color.xyz * u_exposition, color.a );\n\t\t}", F.registerNodeType("texture/exposition", C), K.title = "Tone Mapping", K.desc = "Applies Tone Mapping to convert from high to low", K.widgets_info = {precision:{widget:"combo", values:c.MODE_VALUES}}, K.prototype.onGetInputs = - function() { - return [["enabled", "boolean"]]; - }, K.prototype.onExecute = function() { - var a = this.getInputData(0); - if (a && this.isOutputConnected(0)) { - if (this.properties.precision === c.PASS_THROUGH || !1 === this.getInputOrProperty("enabled")) { - this.setOutputData(0, a); - } else { - var b = this._temp_texture; - b && b.width == a.width && b.height == a.height && b.type == a.type || (b = this._temp_texture = new GL.Texture(a.width, a.height, {type:a.type, format:gl.RGBA, filter:gl.LINEAR})); - var d = this.getInputData(1); - null == d && (d = this.properties.average_lum); - var f = this._uniforms, e = null; - d.constructor === Number ? (this.properties.average_lum = d, f.u_average_lum = this.properties.average_lum, e = K._shader, e || (e = K._shader = new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER, K.pixel_shader))) : d.constructor === GL.Texture && (f.u_average_texture = d.bind(1), e = K._shader_texture, e || (e = K._shader_texture = new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER, K.pixel_shader, {AVG_TEXTURE:""}))); - f.u_lumwhite2 = this.properties.lum_white * this.properties.lum_white; - f.u_scale = this.properties.scale; - f.u_igamma = 1 / this.properties.gamma; - gl.disable(gl.DEPTH_TEST); - b.drawTo(function() { - a.bind(0); - e.uniforms(f).draw(GL.Mesh.getScreenQuad()); - }); - this.setOutputData(0, this._temp_texture); - } - } - }, K.pixel_shader = "precision highp float;\n\t\tvarying vec2 v_coord;\n\t\tuniform sampler2D u_texture;\n\t\tuniform float u_scale;\n\t\t#ifdef AVG_TEXTURE\n\t\t\tuniform sampler2D u_average_texture;\n\t\t#else\n\t\t\tuniform float u_average_lum;\n\t\t#endif\n\t\tuniform float u_lumwhite2;\n\t\tuniform float u_igamma;\n\t\tvec3 RGB2xyY (vec3 rgb)\n\t\t{\n\t\t\t const mat3 RGB2XYZ = mat3(0.4124, 0.3576, 0.1805,\n\t\t\t\t\t\t\t\t\t 0.2126, 0.7152, 0.0722,\n\t\t\t\t\t\t\t\t\t 0.0193, 0.1192, 0.9505);\n\t\t\tvec3 XYZ = RGB2XYZ * rgb;\n\t\t\t\n\t\t\tfloat f = (XYZ.x + XYZ.y + XYZ.z);\n\t\t\treturn vec3(XYZ.x / f,\n\t\t\t\t\t\tXYZ.y / f,\n\t\t\t\t\t\tXYZ.y);\n\t\t}\n\t\t\n\t\tvoid main() {\n\t\t\tvec4 color = texture2D( u_texture, v_coord );\n\t\t\tvec3 rgb = color.xyz;\n\t\t\tfloat average_lum = 0.0;\n\t\t\t#ifdef AVG_TEXTURE\n\t\t\t\tvec3 pixel = texture2D(u_average_texture,vec2(0.5)).xyz;\n\t\t\t\taverage_lum = (pixel.x + pixel.y + pixel.z) / 3.0;\n\t\t\t#else\n\t\t\t\taverage_lum = u_average_lum;\n\t\t\t#endif\n\t\t\t//Ld - this part of the code is the same for both versions\n\t\t\tfloat lum = dot(rgb, vec3(0.2126, 0.7152, 0.0722));\n\t\t\tfloat L = (u_scale / average_lum) * lum;\n\t\t\tfloat Ld = (L * (1.0 + L / u_lumwhite2)) / (1.0 + L);\n\t\t\t//first\n\t\t\t//vec3 xyY = RGB2xyY(rgb);\n\t\t\t//xyY.z *= Ld;\n\t\t\t//rgb = xyYtoRGB(xyY);\n\t\t\t//second\n\t\t\trgb = (rgb / lum) * Ld;\n\t\t\trgb = max(rgb,vec3(0.001));\n\t\t\trgb = pow( rgb, vec3( u_igamma ) );\n\t\t\tgl_FragColor = vec4( rgb, color.a );\n\t\t}", - F.registerNodeType("texture/tonemapping", K), N.title = "Perlin", N.desc = "Generates a perlin noise texture", N.widgets_info = {precision:{widget:"combo", values:c.MODE_VALUES}, width:{type:"Number", precision:0, step:1}, height:{type:"Number", precision:0, step:1}, octaves:{type:"Number", precision:0, step:1, min:1, max:50}}, N.prototype.onGetInputs = function() { - return [["seed", "Number"], ["persistence", "Number"], ["octaves", "Number"], ["scale", "Number"], ["amplitude", "Number"], ["offset", "vec2"]]; - }, N.prototype.onExecute = function() { - if (this.isOutputConnected(0)) { - var a = this.properties.width | 0, b = this.properties.height | 0; - 0 == a && (a = gl.viewport_data[2]); - 0 == b && (b = gl.viewport_data[3]); - var d = c.getTextureType(this.properties.precision), f = this._texture; - f && f.width == a && f.height == b && f.type == d || (f = this._texture = new GL.Texture(a, b, {type:d, format:gl.RGB, filter:gl.LINEAR})); - var e = this.getInputOrProperty("persistence"), g = this.getInputOrProperty("octaves"), l = this.getInputOrProperty("offset"), p = this.getInputOrProperty("scale"), h = this.getInputOrProperty("amplitude"), m = this.getInputOrProperty("seed"); - d = "" + a + b + d + e + g + p + m + l[0] + l[1] + h; - if (d != this._key) { - this._key = d; - var k = this._uniforms; - k.u_persistence = e; - k.u_octaves = g; - k.u_offset.set(l); - k.u_scale = p; - k.u_amplitude = h; - k.u_seed = 128 * m; - k.u_viewport[0] = a; - k.u_viewport[1] = b; - var n = N._shader; - n || (n = N._shader = new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER, N.pixel_shader)); - gl.disable(gl.BLEND); - gl.disable(gl.DEPTH_TEST); - f.drawTo(function() { - n.uniforms(k).draw(GL.Mesh.getScreenQuad()); - }); - } - this.setOutputData(0, f); - } - }, N.pixel_shader = "precision highp float;\n\t\tvarying vec2 v_coord;\n\t\tuniform vec2 u_offset;\n\t\tuniform float u_scale;\n\t\tuniform float u_persistence;\n\t\tuniform int u_octaves;\n\t\tuniform float u_amplitude;\n\t\tuniform vec2 u_viewport;\n\t\tuniform float u_seed;\n\t\t#define M_PI 3.14159265358979323846\n\t\t\n\t\tfloat rand(vec2 c){\treturn fract(sin(dot(c.xy ,vec2( 12.9898 + u_seed,78.233 + u_seed))) * 43758.5453); }\n\t\t\n\t\tfloat noise(vec2 p, float freq ){\n\t\t\tfloat unit = u_viewport.x/freq;\n\t\t\tvec2 ij = floor(p/unit);\n\t\t\tvec2 xy = mod(p,unit)/unit;\n\t\t\t//xy = 3.*xy*xy-2.*xy*xy*xy;\n\t\t\txy = .5*(1.-cos(M_PI*xy));\n\t\t\tfloat a = rand((ij+vec2(0.,0.)));\n\t\t\tfloat b = rand((ij+vec2(1.,0.)));\n\t\t\tfloat c = rand((ij+vec2(0.,1.)));\n\t\t\tfloat d = rand((ij+vec2(1.,1.)));\n\t\t\tfloat x1 = mix(a, b, xy.x);\n\t\t\tfloat x2 = mix(c, d, xy.x);\n\t\t\treturn mix(x1, x2, xy.y);\n\t\t}\n\t\t\n\t\tfloat pNoise(vec2 p, int res){\n\t\t\tfloat persistance = u_persistence;\n\t\t\tfloat n = 0.;\n\t\t\tfloat normK = 0.;\n\t\t\tfloat f = 4.;\n\t\t\tfloat amp = 1.0;\n\t\t\tint iCount = 0;\n\t\t\tfor (int i = 0; i<50; i++){\n\t\t\t\tn+=amp*noise(p, f);\n\t\t\t\tf*=2.;\n\t\t\t\tnormK+=amp;\n\t\t\t\tamp*=persistance;\n\t\t\t\tif (iCount >= res)\n\t\t\t\t\tbreak;\n\t\t\t\tiCount++;\n\t\t\t}\n\t\t\tfloat nf = n/normK;\n\t\t\treturn nf*nf*nf*nf;\n\t\t}\n\t\tvoid main() {\n\t\t\tvec2 uv = v_coord * u_scale * u_viewport + u_offset * u_scale;\n\t\t\tvec4 color = vec4( pNoise( uv, u_octaves ) * u_amplitude );\n\t\t\tgl_FragColor = color;\n\t\t}", - F.registerNodeType("texture/perlin", N), O.title = "Canvas2D", O.desc = "Executes Canvas2D code inside a texture or the viewport.", O.help = "Set width and height to 0 to match viewport size.", O.widgets_info = {precision:{widget:"combo", values:c.MODE_VALUES}, code:{type:"code"}, width:{type:"Number", precision:0, step:1}, height:{type:"Number", precision:0, step:1}}, O.prototype.onPropertyChanged = function(a, b) { - "code" == a && this.compileCode(b); - }, O.prototype.compileCode = function(a) { - this._func = null; - if (F.allow_scripts) { - try { - this._func = new Function("canvas", "ctx", "time", "script", "v", a), this.boxcolor = "#00FF00"; - } catch (R) { - this.boxcolor = "#FF0000", console.error("Error parsing script"), console.error(R); - } - } - }, O.prototype.onExecute = function() { - var a = this._func; - a && this.isOutputConnected(0) && this.executeDraw(a); - }, O.prototype.executeDraw = function(a) { - var b = this.properties.width || gl.canvas.width, d = this.properties.height || gl.canvas.height, f = this._temp_texture, e = c.getTextureType(this.properties.precision); - f && f.width == b && f.height == d && f.type == e || (f = this._temp_texture = new GL.Texture(b, d, {format:gl.RGBA, filter:gl.LINEAR, type:e})); - var g = this.getInputData(0), l = this.properties, p = this, h = this.graph.getTime(), m = gl, k = gl.canvas; - if (this.properties.use_html_canvas || !v.enableWebGLCanvas) { - this._canvas ? (k = this._canvas, m = this._ctx) : (k = this._canvas = createCanvas(b.height), m = this._ctx = k.getContext("2d")), k.width = b, k.height = d; - } - if (m == gl) { - f.drawTo(function() { - gl.start2D(); - l.clear && (gl.clearColor(0, 0, 0, 0), gl.clear(gl.COLOR_BUFFER_BIT)); - try { - a.draw ? a.draw.call(p, k, m, h, a, g) : a.call(p, k, m, h, a, g), p.boxcolor = "#00FF00"; - } catch (Q) { - p.boxcolor = "#FF0000", console.error("Error executing script"), console.error(Q); - } - gl.finish2D(); - }); - } else { - l.clear && m.clearRect(0, 0, k.width, k.height); - try { - a.draw ? a.draw.call(this, k, m, h, a, g) : a.call(this, k, m, h, a, g), this.boxcolor = "#00FF00"; - } catch (Q) { - this.boxcolor = "#FF0000", console.error("Error executing script"), console.error(Q); - } - f.uploadImage(k); - } - this.setOutputData(0, f); - }, F.registerNodeType("texture/canvas2D", O), I.title = "Matte", I.desc = "Extracts background", I.widgets_info = {key_color:{widget:"color"}, precision:{widget:"combo", values:c.MODE_VALUES}}, I.prototype.onExecute = function() { - if (this.isOutputConnected(0)) { - var a = this.getInputData(0); - if (this.properties.precision === c.PASS_THROUGH) { - this.setOutputData(0, a); - } else { - if (a) { - this._tex = c.getTargetTexture(a, this._tex, this.properties.precision); - gl.disable(gl.BLEND); - gl.disable(gl.DEPTH_TEST); - this._uniforms || (this._uniforms = {u_texture:0, u_key_color:this.properties.key_color, u_threshold:1, u_slope:1}); - var b = this._uniforms, d = Mesh.getScreenQuad(), f = I._shader; - f || (f = I._shader = new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER, I.pixel_shader)); - b.u_key_color = this.properties.key_color; - b.u_threshold = this.properties.threshold; - b.u_slope = this.properties.slope; - this._tex.drawTo(function() { - a.bind(0); - f.uniforms(b).draw(d); - }); - this.setOutputData(0, this._tex); - } - } - } - }, I.pixel_shader = "precision highp float;\n\t\tvarying vec2 v_coord;\n\t\tuniform sampler2D u_texture;\n\t\tuniform vec3 u_key_color;\n\t\tuniform float u_threshold;\n\t\tuniform float u_slope;\n\t\t\n\t\tvoid main() {\n\t\t\tvec3 color = texture2D( u_texture, v_coord ).xyz;\n\t\t\tfloat diff = length( normalize(color) - normalize(u_key_color) );\n\t\t\tfloat edge = u_threshold * (1.0 - u_slope);\n\t\t\tfloat alpha = smoothstep( edge, u_threshold, diff);\n\t\t\tgl_FragColor = vec4( color, alpha );\n\t\t}", - F.registerNodeType("texture/matte", I), P.title = "CubemapToTexture2D", P.desc = "Transforms a CUBEMAP texture into a TEXTURE2D in Polar Representation", P.prototype.onExecute = function() { - if (this.isOutputConnected(0)) { - var a = this.getInputData(0); - if (a && a.texture_type == GL.TEXTURE_CUBE_MAP) { - !this._last_tex || this._last_tex.height == a.height && this._last_tex.type == a.type || (this._last_tex = null); - var b = this.getInputOrProperty("yaw"); - this._last_tex = GL.Texture.cubemapToTexture2D(a, a.height, this._last_tex, !0, b); - this.setOutputData(0, this._last_tex); - } - } - }, F.registerNodeType("texture/cubemapToTexture2D", P)); -})(this); -(function(v) { - var c = v.LiteGraph; - if ("undefined" != typeof GL) { - var k = function() { - this.addInput("Tex.", "Texture"); - this.addInput("intensity", "number"); - this.addOutput("Texture", "Texture"); - this.properties = {intensity:1, invert:!1, precision:LGraphTexture.DEFAULT}; - k._shader || (k._shader = new GL.Shader(Shader.SCREEN_VERTEX_SHADER, k.pixel_shader)); - }, h = function() { - this.addInput("Texture", "Texture"); - this.addInput("value1", "number"); - this.addInput("value2", "number"); - this.addOutput("Texture", "Texture"); - this.properties = {fx:"halftone", value1:1, value2:1, precision:LGraphTexture.DEFAULT}; - }, m = function() { - this.addInput("Texture", "Texture"); - this.addInput("Blurred", "Texture"); - this.addInput("Mask", "Texture"); - this.addInput("Threshold", "number"); - this.addOutput("Texture", "Texture"); - this.properties = {shape:"", size:10, alpha:1.0, threshold:1.0, high_precision:!1}; - }, t = function() { - this.addInput("Texture", "Texture"); - this.addInput("Aberration", "number"); - this.addInput("Distortion", "number"); - this.addInput("Blur", "number"); - this.addOutput("Texture", "Texture"); - this.properties = {aberration:1.0, distortion:1.0, blur:1.0, precision:LGraphTexture.DEFAULT}; - t._shader || (t._shader = new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER, t.pixel_shader), t._texture = new GL.Texture(3, 1, {format:gl.RGB, wrap:gl.CLAMP_TO_EDGE, magFilter:gl.LINEAR, minFilter:gl.LINEAR, pixel_data:[255, 0, 0, 0, 255, 0, 0, 0, 255]})); - }; - t.title = "Lens"; - t.desc = "Camera Lens distortion"; - t.widgets_info = {precision:{widget:"combo", values:LGraphTexture.MODE_VALUES}}; - t.prototype.onExecute = function() { - var c = this.getInputData(0); - if (this.properties.precision === LGraphTexture.PASS_THROUGH) { - this.setOutputData(0, c); - } else { - if (c) { - this._tex = LGraphTexture.getTargetTexture(c, this._tex, this.properties.precision); - var h = this.properties.aberration; - this.isInputConnected(1) && (h = this.getInputData(1), this.properties.aberration = h); - var m = this.properties.distortion; - this.isInputConnected(2) && (m = this.getInputData(2), this.properties.distortion = m); - var k = this.properties.blur; - this.isInputConnected(3) && (k = this.getInputData(3), this.properties.blur = k); - gl.disable(gl.BLEND); - gl.disable(gl.DEPTH_TEST); - var v = Mesh.getScreenQuad(), A = t._shader; - this._tex.drawTo(function() { - c.bind(0); - A.uniforms({u_texture:0, u_aberration:h, u_distortion:m, u_blur:k}).draw(v); - }); - this.setOutputData(0, this._tex); - } - } - }; - t.pixel_shader = "precision highp float;\n\r\n\t\t\tprecision highp float;\n\r\n\t\t\tvarying vec2 v_coord;\n\r\n\t\t\tuniform sampler2D u_texture;\n\r\n\t\t\tuniform vec2 u_camera_planes;\n\r\n\t\t\tuniform float u_aberration;\n\r\n\t\t\tuniform float u_distortion;\n\r\n\t\t\tuniform float u_blur;\n\r\n\t\t\t\n\r\n\t\t\tvoid main() {\n\r\n\t\t\t\tvec2 coord = v_coord;\n\r\n\t\t\t\tfloat dist = distance(vec2(0.5), coord);\n\r\n\t\t\t\tvec2 dist_coord = coord - vec2(0.5);\n\r\n\t\t\t\tfloat percent = 1.0 + ((0.5 - dist) / 0.5) * u_distortion;\n\r\n\t\t\t\tdist_coord *= percent;\n\r\n\t\t\t\tcoord = dist_coord + vec2(0.5);\n\r\n\t\t\t\tvec4 color = texture2D(u_texture,coord, u_blur * dist);\n\r\n\t\t\t\tcolor.r = texture2D(u_texture,vec2(0.5) + dist_coord * (1.0+0.01*u_aberration), u_blur * dist ).r;\n\r\n\t\t\t\tcolor.b = texture2D(u_texture,vec2(0.5) + dist_coord * (1.0-0.01*u_aberration), u_blur * dist ).b;\n\r\n\t\t\t\tgl_FragColor = color;\n\r\n\t\t\t}\n\r\n\t\t\t"; - c.registerNodeType("fx/lens", t); - v.LGraphFXLens = t; - m.title = "Bokeh"; - m.desc = "applies an Bokeh effect"; - m.widgets_info = {shape:{widget:"texture"}}; - m.prototype.onExecute = function() { - var c = this.getInputData(0), h = this.getInputData(1), k = this.getInputData(2); - if (c && k && this.properties.shape) { - h || (h = c); - var t = LGraphTexture.getTexture(this.properties.shape); - if (t) { - var v = this.properties.threshold; - this.isInputConnected(3) && (v = this.getInputData(3), this.properties.threshold = v); - var A = gl.UNSIGNED_BYTE; - this.properties.high_precision && (A = gl.half_float_ext ? gl.HALF_FLOAT_OES : gl.FLOAT); - this._temp_texture && this._temp_texture.type == A && this._temp_texture.width == c.width && this._temp_texture.height == c.height || (this._temp_texture = new GL.Texture(c.width, c.height, {type:A, format:gl.RGBA, filter:gl.LINEAR})); - var e = m._first_shader; - e || (e = m._first_shader = new GL.Shader(Shader.SCREEN_VERTEX_SHADER, m._first_pixel_shader)); - var y = m._second_shader; - y || (y = m._second_shader = new GL.Shader(m._second_vertex_shader, m._second_pixel_shader)); - var w = this._points_mesh; - w && w._width == c.width && w._height == c.height && 2 == w._spacing || (w = this.createPointsMesh(c.width, c.height, 2)); - var z = Mesh.getScreenQuad(), l = this.properties.size, p = this.properties.alpha; - gl.disable(gl.DEPTH_TEST); - gl.disable(gl.BLEND); - this._temp_texture.drawTo(function() { - c.bind(0); - h.bind(1); - k.bind(2); - e.uniforms({u_texture:0, u_texture_blur:1, u_mask:2, u_texsize:[c.width, c.height]}).draw(z); - }); - this._temp_texture.drawTo(function() { - gl.enable(gl.BLEND); - gl.blendFunc(gl.ONE, gl.ONE); - c.bind(0); - t.bind(3); - y.uniforms({u_texture:0, u_mask:2, u_shape:3, u_alpha:p, u_threshold:v, u_pointSize:l, u_itexsize:[1.0 / c.width, 1.0 / c.height]}).draw(w, gl.POINTS); - }); - this.setOutputData(0, this._temp_texture); - } - } else { - this.setOutputData(0, c); - } - }; - m.prototype.createPointsMesh = function(c, h, m) { - for (var g = Math.round(c / m), k = Math.round(h / m), t = new Float32Array(g * k * 2), e = -1, v = 2 / c * m, w = 2 / h * m, D = 0; D < k; ++D) { - for (var l = -1, p = 0; p < g; ++p) { - var r = D * g * 2 + 2 * p; - t[r] = l; - t[r + 1] = e; - l += v; - } - e += w; - } - this._points_mesh = GL.Mesh.load({vertices2D:t}); - this._points_mesh._width = c; - this._points_mesh._height = h; - this._points_mesh._spacing = m; - return this._points_mesh; - }; - m._first_pixel_shader = "precision highp float;\n\r\n\t\t\tprecision highp float;\n\r\n\t\t\tvarying vec2 v_coord;\n\r\n\t\t\tuniform sampler2D u_texture;\n\r\n\t\t\tuniform sampler2D u_texture_blur;\n\r\n\t\t\tuniform sampler2D u_mask;\n\r\n\t\t\t\n\r\n\t\t\tvoid main() {\n\r\n\t\t\t\tvec4 color = texture2D(u_texture, v_coord);\n\r\n\t\t\t\tvec4 blurred_color = texture2D(u_texture_blur, v_coord);\n\r\n\t\t\t\tfloat mask = texture2D(u_mask, v_coord).x;\n\r\n\t\t\t gl_FragColor = mix(color, blurred_color, mask);\n\r\n\t\t\t}\n\r\n\t\t\t"; - m._second_vertex_shader = "precision highp float;\n\r\n\t\t\tattribute vec2 a_vertex2D;\n\r\n\t\t\tvarying vec4 v_color;\n\r\n\t\t\tuniform sampler2D u_texture;\n\r\n\t\t\tuniform sampler2D u_mask;\n\r\n\t\t\tuniform vec2 u_itexsize;\n\r\n\t\t\tuniform float u_pointSize;\n\r\n\t\t\tuniform float u_threshold;\n\r\n\t\t\tvoid main() {\n\r\n\t\t\t\tvec2 coord = a_vertex2D * 0.5 + 0.5;\n\r\n\t\t\t\tv_color = texture2D( u_texture, coord );\n\r\n\t\t\t\tv_color += texture2D( u_texture, coord + vec2(u_itexsize.x, 0.0) );\n\r\n\t\t\t\tv_color += texture2D( u_texture, coord + vec2(0.0, u_itexsize.y));\n\r\n\t\t\t\tv_color += texture2D( u_texture, coord + u_itexsize);\n\r\n\t\t\t\tv_color *= 0.25;\n\r\n\t\t\t\tfloat mask = texture2D(u_mask, coord).x;\n\r\n\t\t\t\tfloat luminance = length(v_color) * mask;\n\r\n\t\t\t\t/*luminance /= (u_pointSize*u_pointSize)*0.01 */;\n\r\n\t\t\t\tluminance -= u_threshold;\n\r\n\t\t\t\tif(luminance < 0.0)\n\r\n\t\t\t\t{\n\r\n\t\t\t\t\tgl_Position.x = -100.0;\n\r\n\t\t\t\t\treturn;\n\r\n\t\t\t\t}\n\r\n\t\t\t\tgl_PointSize = u_pointSize;\n\r\n\t\t\t\tgl_Position = vec4(a_vertex2D,0.0,1.0);\n\r\n\t\t\t}\n\r\n\t\t\t"; - m._second_pixel_shader = "precision highp float;\n\r\n\t\t\tvarying vec4 v_color;\n\r\n\t\t\tuniform sampler2D u_shape;\n\r\n\t\t\tuniform float u_alpha;\n\r\n\t\t\t\n\r\n\t\t\tvoid main() {\n\r\n\t\t\t\tvec4 color = texture2D( u_shape, gl_PointCoord );\n\r\n\t\t\t\tcolor *= v_color * u_alpha;\n\r\n\t\t\t\tgl_FragColor = color;\n\r\n\t\t\t}\n"; - c.registerNodeType("fx/bokeh", m); - v.LGraphFXBokeh = m; - h.title = "FX"; - h.desc = "applies an FX from a list"; - h.widgets_info = {fx:{widget:"combo", values:["halftone", "pixelate", "lowpalette", "noise", "gamma"]}, precision:{widget:"combo", values:LGraphTexture.MODE_VALUES}}; - h.shaders = {}; - h.prototype.onExecute = function() { - if (this.isOutputConnected(0)) { - var c = this.getInputData(0); - if (this.properties.precision === LGraphTexture.PASS_THROUGH) { - this.setOutputData(0, c); - } else { - if (c) { - this._tex = LGraphTexture.getTargetTexture(c, this._tex, this.properties.precision); - var m = this.properties.value1; - this.isInputConnected(1) && (m = this.getInputData(1), this.properties.value1 = m); - var k = this.properties.value2; - this.isInputConnected(2) && (k = this.getInputData(2), this.properties.value2 = k); - var t = this.properties.fx, E = h.shaders[t]; - if (!E) { - var A = h["pixel_shader_" + t]; - if (!A) { - return; - } - E = h.shaders[t] = new GL.Shader(Shader.SCREEN_VERTEX_SHADER, A); - } - gl.disable(gl.BLEND); - gl.disable(gl.DEPTH_TEST); - var e = Mesh.getScreenQuad(); - var y = v.LS && LS.Renderer._current_camera ? [LS.Renderer._current_camera.near, LS.Renderer._current_camera.far] : [1, 100]; - var w = null; - "noise" == t && (w = LGraphTexture.getNoiseTexture()); - this._tex.drawTo(function() { - c.bind(0); - "noise" == t && w.bind(1); - E.uniforms({u_texture:0, u_noise:1, u_size:[c.width, c.height], u_rand:[Math.random(), Math.random()], u_value1:m, u_value2:k, u_camera_planes:y}).draw(e); - }); - this.setOutputData(0, this._tex); - } - } - } - }; - h.pixel_shader_halftone = "precision highp float;\n\r\n\t\t\tvarying vec2 v_coord;\n\r\n\t\t\tuniform sampler2D u_texture;\n\r\n\t\t\tuniform vec2 u_camera_planes;\n\r\n\t\t\tuniform vec2 u_size;\n\r\n\t\t\tuniform float u_value1;\n\r\n\t\t\tuniform float u_value2;\n\r\n\t\t\t\n\r\n\t\t\tfloat pattern() {\n\r\n\t\t\t\tfloat s = sin(u_value1 * 3.1415), c = cos(u_value1 * 3.1415);\n\r\n\t\t\t\tvec2 tex = v_coord * u_size.xy;\n\r\n\t\t\t\tvec2 point = vec2(\n\r\n\t\t\t\t c * tex.x - s * tex.y ,\n\r\n\t\t\t\t s * tex.x + c * tex.y \n\r\n\t\t\t\t) * u_value2;\n\r\n\t\t\t\treturn (sin(point.x) * sin(point.y)) * 4.0;\n\r\n\t\t\t}\n\r\n\t\t\tvoid main() {\n\r\n\t\t\t\tvec4 color = texture2D(u_texture, v_coord);\n\r\n\t\t\t\tfloat average = (color.r + color.g + color.b) / 3.0;\n\r\n\t\t\t\tgl_FragColor = vec4(vec3(average * 10.0 - 5.0 + pattern()), color.a);\n\r\n\t\t\t}\n"; - h.pixel_shader_pixelate = "precision highp float;\n\r\n\t\t\tvarying vec2 v_coord;\n\r\n\t\t\tuniform sampler2D u_texture;\n\r\n\t\t\tuniform vec2 u_camera_planes;\n\r\n\t\t\tuniform vec2 u_size;\n\r\n\t\t\tuniform float u_value1;\n\r\n\t\t\tuniform float u_value2;\n\r\n\t\t\t\n\r\n\t\t\tvoid main() {\n\r\n\t\t\t\tvec2 coord = vec2( floor(v_coord.x * u_value1) / u_value1, floor(v_coord.y * u_value2) / u_value2 );\n\r\n\t\t\t\tvec4 color = texture2D(u_texture, coord);\n\r\n\t\t\t\tgl_FragColor = color;\n\r\n\t\t\t}\n"; - h.pixel_shader_lowpalette = "precision highp float;\n\r\n\t\t\tvarying vec2 v_coord;\n\r\n\t\t\tuniform sampler2D u_texture;\n\r\n\t\t\tuniform vec2 u_camera_planes;\n\r\n\t\t\tuniform vec2 u_size;\n\r\n\t\t\tuniform float u_value1;\n\r\n\t\t\tuniform float u_value2;\n\r\n\t\t\t\n\r\n\t\t\tvoid main() {\n\r\n\t\t\t\tvec4 color = texture2D(u_texture, v_coord);\n\r\n\t\t\t\tgl_FragColor = floor(color * u_value1) / u_value1;\n\r\n\t\t\t}\n"; - h.pixel_shader_noise = "precision highp float;\n\r\n\t\t\tvarying vec2 v_coord;\n\r\n\t\t\tuniform sampler2D u_texture;\n\r\n\t\t\tuniform sampler2D u_noise;\n\r\n\t\t\tuniform vec2 u_size;\n\r\n\t\t\tuniform float u_value1;\n\r\n\t\t\tuniform float u_value2;\n\r\n\t\t\tuniform vec2 u_rand;\n\r\n\t\t\t\n\r\n\t\t\tvoid main() {\n\r\n\t\t\t\tvec4 color = texture2D(u_texture, v_coord);\n\r\n\t\t\t\tvec3 noise = texture2D(u_noise, v_coord * vec2(u_size.x / 512.0, u_size.y / 512.0) + u_rand).xyz - vec3(0.5);\n\r\n\t\t\t\tgl_FragColor = vec4( color.xyz + noise * u_value1, color.a );\n\r\n\t\t\t}\n"; - h.pixel_shader_gamma = "precision highp float;\n\r\n\t\t\tvarying vec2 v_coord;\n\r\n\t\t\tuniform sampler2D u_texture;\n\r\n\t\t\tuniform float u_value1;\n\r\n\t\t\t\n\r\n\t\t\tvoid main() {\n\r\n\t\t\t\tvec4 color = texture2D(u_texture, v_coord);\n\r\n\t\t\t\tfloat gamma = 1.0 / u_value1;\n\r\n\t\t\t\tgl_FragColor = vec4( pow( color.xyz, vec3(gamma) ), color.a );\n\r\n\t\t\t}\n"; - c.registerNodeType("fx/generic", h); - v.LGraphFXGeneric = h; - k.title = "Vigneting"; - k.desc = "Vigneting"; - k.widgets_info = {precision:{widget:"combo", values:LGraphTexture.MODE_VALUES}}; - k.prototype.onExecute = function() { - var c = this.getInputData(0); - if (this.properties.precision === LGraphTexture.PASS_THROUGH) { - this.setOutputData(0, c); - } else { - if (c) { - this._tex = LGraphTexture.getTargetTexture(c, this._tex, this.properties.precision); - var h = this.properties.intensity; - this.isInputConnected(1) && (h = this.getInputData(1), this.properties.intensity = h); - gl.disable(gl.BLEND); - gl.disable(gl.DEPTH_TEST); - var m = Mesh.getScreenQuad(), t = k._shader, v = this.properties.invert; - this._tex.drawTo(function() { - c.bind(0); - t.uniforms({u_texture:0, u_intensity:h, u_isize:[1 / c.width, 1 / c.height], u_invert:v ? 1 : 0}).draw(m); - }); - this.setOutputData(0, this._tex); - } - } - }; - k.pixel_shader = "precision highp float;\n\r\n\t\t\tprecision highp float;\n\r\n\t\t\tvarying vec2 v_coord;\n\r\n\t\t\tuniform sampler2D u_texture;\n\r\n\t\t\tuniform float u_intensity;\n\r\n\t\t\tuniform int u_invert;\n\r\n\t\t\t\n\r\n\t\t\tvoid main() {\n\r\n\t\t\t\tfloat luminance = 1.0 - length( v_coord - vec2(0.5) ) * 1.414;\n\r\n\t\t\t\tvec4 color = texture2D(u_texture, v_coord);\n\r\n\t\t\t\tif(u_invert == 1)\n\r\n\t\t\t\t\tluminance = 1.0 - luminance;\n\r\n\t\t\t\tluminance = mix(1.0, luminance, u_intensity);\n\r\n\t\t\t gl_FragColor = vec4( luminance * color.xyz, color.a);\n\r\n\t\t\t}\n\r\n\t\t\t"; - c.registerNodeType("fx/vigneting", k); - v.LGraphFXVigneting = k; - } -})(this); -(function(v) { - function c(c) { - this.cmd = this.channel = 0; - this.data = new Uint32Array(3); - c && this.setup(c); - } - function k(c, e) { - navigator.requestMIDIAccess ? (this.on_ready = c, this.state = {note:[], cc:[]}, navigator.requestMIDIAccess().then(this.onMIDISuccess.bind(this), this.onMIDIFailure.bind(this))) : (this.error = "not suppoorted", e ? e("Not supported") : console.error("MIDI NOT SUPPORTED, enable by chrome://flags")); - } - function h() { - this.addOutput("on_midi", w.EVENT); - this.addOutput("out", "midi"); - this.properties = {port:0}; - this._current_midi_event = this._last_midi_event = null; - this.boxcolor = "#AAA"; - this._last_time = 0; - var c = this; - new k(function(e) { - c._midi = e; - if (c._waiting) { - c.onStart(); - } - c._waiting = !1; - }); - } - function m() { - this.addInput("send", w.EVENT); - this.properties = {port:0}; - var c = this; - new k(function(e) { - c._midi = e; - }); - } - function t() { - this.addInput("on_midi", w.EVENT); - this._str = ""; - this.size = [200, 40]; - } - function g() { - this.properties = {channel:-1, cmd:-1, min_value:-1, max_value:-1}; - var c = this; - this._learning = !1; - this.addWidget("button", "Learn", "", function() { - c._learning = !0; - c.boxcolor = "#FA3"; - }); - this.addInput("in", w.EVENT); - this.addOutput("on_midi", w.EVENT); - this.boxcolor = "#AAA"; - } - function D() { - this.properties = {channel:0, cmd:144, value1:1, value2:1}; - this.addInput("send", w.EVENT); - this.addInput("assign", w.EVENT); - this.addOutput("on_midi", w.EVENT); - this.midi_event = new c; - this.gate = !1; - } - function B() { - this.properties = {cc:1, value:0}; - this.addOutput("value", "number"); - } - function x() { - this.addInput("generate", w.ACTION); - this.addInput("scale", "string"); - this.addInput("octave", "number"); - this.addOutput("note", w.EVENT); - this.properties = {notes:"A,A#,B,C,C#,D,D#,E,F,F#,G,G#", octave:2, duration:0.5, mode:"sequence"}; - this.notes_pitches = x.processScale(this.properties.notes); - this.sequence_index = 0; - } - function E() { - this.properties = {amount:0}; - this.addInput("in", w.ACTION); - this.addInput("amount", "number"); - this.addOutput("out", w.EVENT); - this.midi_event = new c; - } - function A() { - this.properties = {scale:"A,A#,B,C,C#,D,D#,E,F,F#,G,G#"}; - this.addInput("note", w.ACTION); - this.addInput("scale", "string"); - this.addOutput("out", w.EVENT); - this.valid_notes = Array(12); - this.offset_notes = Array(12); - this.processScale(this.properties.scale); - } - function e() { - this.properties = {volume:0.5, duration:1}; - this.addInput("note", w.ACTION); - this.addInput("volume", "number"); - this.addInput("duration", "number"); - this.addOutput("note", w.EVENT); - "undefined" == typeof AudioSynth ? (console.error("Audiosynth.js not included, LGMidiPlay requires that library"), this.boxcolor = "red") : this.instrument = (this.synth = new AudioSynth).createInstrument("piano"); - } - function y() { - this.properties = {num_octaves:2, start_octave:2}; - this.addInput("note", w.ACTION); - this.addInput("reset", w.ACTION); - this.addOutput("note", w.EVENT); - this.size = [400, 100]; - this.keys = []; - this._last_key = -1; - } - var w = v.LiteGraph; - w.MIDIEvent = c; - c.prototype.fromJSON = function(c) { - this.setup(c.data); - }; - c.prototype.setup = function(e) { - var l = e; - e.constructor === Object && (l = e.data); - this.data.set(l); - this.status = e = l[0]; - l = e & 240; - this.cmd = 240 <= e ? e : l; - this.cmd == c.NOTEON && 0 == this.velocity && (this.cmd = c.NOTEOFF); - this.cmd_str = c.commands[this.cmd] || ""; - if (l >= c.NOTEON || l <= c.NOTEOFF) { - this.channel = e & 15; - } - }; - Object.defineProperty(c.prototype, "velocity", {get:function() { - return this.cmd == c.NOTEON ? this.data[2] : -1; - }, set:function(c) { - this.data[2] = c; - }, enumerable:!0}); - c.notes = "A A# B C C# D D# E F F# G G#".split(" "); - c.note_to_index = {A:0, "A#":1, B:2, C:3, "C#":4, D:5, "D#":6, E:7, F:8, "F#":9, G:10, "G#":11}; - Object.defineProperty(c.prototype, "note", {get:function() { - return this.cmd != c.NOTEON ? -1 : c.toNoteString(this.data[1], !0); - }, set:function(c) { - throw "notes cannot be assigned this way, must modify the data[1]"; - }, enumerable:!0}); - Object.defineProperty(c.prototype, "octave", {get:function() { - return this.cmd != c.NOTEON ? -1 : Math.floor((this.data[1] - 24) / 12 + 1); - }, set:function(c) { - throw "octave cannot be assigned this way, must modify the data[1]"; - }, enumerable:!0}); - c.prototype.getPitch = function() { - return 440 * Math.pow(2, (this.data[1] - 69) / 12); - }; - c.computePitch = function(c) { - return 440 * Math.pow(2, (c - 69) / 12); - }; - c.prototype.getCC = function() { - return this.data[1]; - }; - c.prototype.getCCValue = function() { - return this.data[2]; - }; - c.prototype.getPitchBend = function() { - return this.data[1] + (this.data[2] << 7) - 8192; - }; - c.computePitchBend = function(c, e) { - return c + (e << 7) - 8192; - }; - c.prototype.setCommandFromString = function(e) { - this.cmd = c.computeCommandFromString(e); - }; - c.computeCommandFromString = function(e) { - if (!e) { - return 0; - } - if (e && e.constructor === Number) { - return e; - } - e = e.toUpperCase(); - switch(e) { - case "NOTE ON": - case "NOTEON": - return c.NOTEON; - case "NOTE OFF": - case "NOTEOFF": - return c.NOTEON; - case "KEY PRESSURE": - case "KEYPRESSURE": - return c.KEYPRESSURE; - case "CONTROLLER CHANGE": - case "CONTROLLERCHANGE": - case "CC": - return c.CONTROLLERCHANGE; - case "PROGRAM CHANGE": - case "PROGRAMCHANGE": - case "PC": - return c.PROGRAMCHANGE; - case "CHANNEL PRESSURE": - case "CHANNELPRESSURE": - return c.CHANNELPRESSURE; - case "PITCH BEND": - case "PITCHBEND": - return c.PITCHBEND; - case "TIME TICK": - case "TIMETICK": - return c.TIMETICK; - default: - return Number(e); - } - }; - c.toNoteString = function(e, g) { - e = Math.round(e); - var l = Math.floor((e - 24) / 12 + 1); - e = (e - 21) % 12; - 0 > e && (e = 12 + e); - return c.notes[e] + (g ? "" : l); - }; - c.NoteStringToPitch = function(e) { - e = e.toUpperCase(); - var g = e[0], l = 4; - "#" == e[1] ? (g += "#", 2 < e.length && (l = Number(e[2]))) : 1 < e.length && (l = Number(e[1])); - e = c.note_to_index[g]; - return null == e ? null : 12 * (l - 1) + e + 21; - }; - c.prototype.toString = function() { - var e = "" + this.channel + ". "; - switch(this.cmd) { - case c.NOTEON: - e += "NOTEON " + c.toNoteString(this.data[1]); - break; - case c.NOTEOFF: - e += "NOTEOFF " + c.toNoteString(this.data[1]); - break; - case c.CONTROLLERCHANGE: - e += "CC " + this.data[1] + " " + this.data[2]; - break; - case c.PROGRAMCHANGE: - e += "PC " + this.data[1]; - break; - case c.PITCHBEND: - e += "PITCHBEND " + this.getPitchBend(); - break; - case c.KEYPRESSURE: - e += "KEYPRESS " + this.data[1]; - } - return e; - }; - c.prototype.toHexString = function() { - for (var c = "", e = 0; e < this.data.length; e++) { - c += this.data[e].toString(16) + " "; - } - }; - c.prototype.toJSON = function() { - return {data:[this.data[0], this.data[1], this.data[2]], object_class:"MIDIEvent"}; - }; - c.NOTEOFF = 128; - c.NOTEON = 144; - c.KEYPRESSURE = 160; - c.CONTROLLERCHANGE = 176; - c.PROGRAMCHANGE = 192; - c.CHANNELPRESSURE = 208; - c.PITCHBEND = 224; - c.TIMETICK = 248; - c.commands = {128:"note off", 144:"note on", 160:"key pressure", 176:"controller change", 192:"program change", 208:"channel pressure", 224:"pitch bend", 240:"system", 242:"Song pos", 243:"Song select", 246:"Tune request", 248:"time tick", 250:"Start Song", 251:"Continue Song", 252:"Stop Song", 254:"Sensing", 255:"Reset"}; - c.commands_short = {128:"NOTEOFF", 144:"NOTEOFF", 160:"KEYP", 176:"CC", 192:"PC", 208:"CP", 224:"PB", 240:"SYS", 242:"POS", 243:"SELECT", 246:"TUNEREQ", 248:"TT", 250:"START", 251:"CONTINUE", 252:"STOP", 254:"SENS", 255:"RESET"}; - c.commands_reversed = {}; - for (var z in c.commands) { - c.commands_reversed[c.commands[z]] = z; - } - k.input = null; - k.MIDIEvent = c; - k.prototype.onMIDISuccess = function(c) { - console.log("MIDI ready!"); - console.log(c); - this.midi = c; - this.updatePorts(); - if (this.on_ready) { - this.on_ready(this); - } - }; - k.prototype.updatePorts = function() { - var c = this.midi; - this.input_ports = c.inputs; - for (var e = 0, g = this.input_ports.values(), f = g.next(); f && !1 === f.done;) { - f = f.value, console.log("Input port [type:'" + f.type + "'] id:'" + f.id + "' manufacturer:'" + f.manufacturer + "' name:'" + f.name + "' version:'" + f.version + "'"), e++, f = g.next(); - } - this.num_input_ports = e; - e = 0; - this.output_ports = c.outputs; - g = this.output_ports.values(); - for (f = g.next(); f && !1 === f.done;) { - f = f.value, console.log("Output port [type:'" + f.type + "'] id:'" + f.id + "' manufacturer:'" + f.manufacturer + "' name:'" + f.name + "' version:'" + f.version + "'"), e++, f = g.next(); - } - this.num_output_ports = e; - }; - k.prototype.onMIDIFailure = function(c) { - console.error("Failed to get MIDI access - " + c); - }; - k.prototype.openInputPort = function(e, g) { - e = this.input_ports.get("input-" + e); - if (!e) { - return !1; - } - k.input = this; - var l = this; - e.onmidimessage = function(f) { - var a = new c(f.data); - l.updateState(a); - g && g(f.data, a); - if (k.on_message) { - k.on_message(f.data, a); - } - }; - console.log("port open: ", e); - return !0; - }; - k.parseMsg = function(c) { - }; - k.prototype.updateState = function(e) { - switch(e.cmd) { - case c.NOTEON: - this.state.note[e.value1 | 0] = e.value2; - break; - case c.NOTEOFF: - this.state.note[e.value1 | 0] = 0; - break; - case c.CONTROLLERCHANGE: - this.state.cc[e.getCC()] = e.getCCValue(); - } - }; - k.prototype.sendMIDI = function(e, g) { - g && (e = this.output_ports.get("output-" + e)) && (k.output = this, g.constructor === c ? e.send(g.data) : e.send(g)); - }; - h.MIDIInterface = k; - h.title = "MIDI Input"; - h.desc = "Reads MIDI from a input port"; - h.color = "#243"; - h.prototype.getPropertyInfo = function(c) { - if (this._midi && "port" == c) { - c = {}; - for (var e = 0; e < this._midi.input_ports.size; ++e) { - var g = this._midi.input_ports.get("input-" + e); - c[e] = e + ".- " + g.name + " version:" + g.version; - } - return {type:"enum", values:c}; - } - }; - h.prototype.onStart = function() { - this._midi ? this._midi.openInputPort(this.properties.port, this.onMIDIEvent.bind(this)) : this._waiting = !0; - }; - h.prototype.onMIDIEvent = function(e, g) { - this._last_midi_event = g; - this.boxcolor = "#AFA"; - this._last_time = w.getTime(); - this.trigger("on_midi", g); - g.cmd == c.NOTEON ? this.trigger("on_noteon", g) : g.cmd == c.NOTEOFF ? this.trigger("on_noteoff", g) : g.cmd == c.CONTROLLERCHANGE ? this.trigger("on_cc", g) : g.cmd == c.PROGRAMCHANGE ? this.trigger("on_pc", g) : g.cmd == c.PITCHBEND && this.trigger("on_pitchbend", g); - }; - h.prototype.onDrawBackground = function(c) { - this.boxcolor = "#AAA"; - if (!this.flags.collapsed && this._last_midi_event) { - c.fillStyle = "white"; - var e = w.getTime(); - e = 1.0 - Math.max(0, 0.001 * (e - this._last_time)); - if (0 < e) { - var g = c.globalAlpha; - c.globalAlpha *= e; - c.font = "12px Tahoma"; - c.fillText(this._last_midi_event.toString(), 2, 0.5 * this.size[1] + 3); - c.globalAlpha = g; - } - } - }; - h.prototype.onExecute = function() { - if (this.outputs) { - for (var c = this._last_midi_event, e = 0; e < this.outputs.length; ++e) { - switch(this.outputs[e].name) { - case "midi": - var g = this._midi; - break; - case "last_midi": - g = c; - break; - default: - continue; - } - this.setOutputData(e, g); - } - } - }; - h.prototype.onGetOutputs = function() { - return [["last_midi", "midi"], ["on_midi", w.EVENT], ["on_noteon", w.EVENT], ["on_noteoff", w.EVENT], ["on_cc", w.EVENT], ["on_pc", w.EVENT], ["on_pitchbend", w.EVENT]]; - }; - w.registerNodeType("midi/input", h); - m.MIDIInterface = k; - m.title = "MIDI Output"; - m.desc = "Sends MIDI to output channel"; - m.color = "#243"; - m.prototype.getPropertyInfo = function(c) { - if (this._midi && "port" == c) { - c = {}; - for (var e = 0; e < this._midi.output_ports.size; ++e) { - var g = this._midi.output_ports.get(e); - c[e] = e + ".- " + g.name + " version:" + g.version; - } - return {type:"enum", values:c}; - } - }; - m.prototype.onAction = function(c, e) { - this._midi && ("send" == c && this._midi.sendMIDI(this.port, e), this.trigger("midi", e)); - }; - m.prototype.onGetInputs = function() { - return [["send", w.ACTION]]; - }; - m.prototype.onGetOutputs = function() { - return [["on_midi", w.EVENT]]; - }; - w.registerNodeType("midi/output", m); - t.title = "MIDI Show"; - t.desc = "Shows MIDI in the graph"; - t.color = "#243"; - t.prototype.getTitle = function() { - return this.flags.collapsed ? this._str : this.title; - }; - t.prototype.onAction = function(e, g) { - g && (this._str = g.constructor === c ? g.toString() : "???"); - }; - t.prototype.onDrawForeground = function(c) { - this._str && !this.flags.collapsed && (c.font = "30px Arial", c.fillText(this._str, 10, 0.8 * this.size[1])); - }; - t.prototype.onGetInputs = function() { - return [["in", w.ACTION]]; - }; - t.prototype.onGetOutputs = function() { - return [["on_midi", w.EVENT]]; - }; - w.registerNodeType("midi/show", t); - g.title = "MIDI Filter"; - g.desc = "Filters MIDI messages"; - g.color = "#243"; - g["@cmd"] = {type:"enum", title:"Command", values:c.commands_reversed}; - g.prototype.getTitle = function() { - var e = -1 == this.properties.cmd ? "Nothing" : c.commands_short[this.properties.cmd] || "Unknown"; - -1 != this.properties.min_value && -1 != this.properties.max_value && (e += " " + (this.properties.min_value == this.properties.max_value ? this.properties.max_value : this.properties.min_value + ".." + this.properties.max_value)); - return "Filter: " + e; - }; - g.prototype.onPropertyChanged = function(e, g) { - "cmd" == e && (e = Number(g), isNaN(e) && (e = c.commands[g] || 0), this.properties.cmd = e); - }; - g.prototype.onAction = function(e, g) { - if (g && g.constructor === c) { - if (this._learning) { - this._learning = !1, this.boxcolor = "#AAA", this.properties.channel = g.channel, this.properties.cmd = g.cmd, this.properties.min_value = this.properties.max_value = g.data[1]; - } else { - if (-1 != this.properties.channel && g.channel != this.properties.channel || -1 != this.properties.cmd && g.cmd != this.properties.cmd || -1 != this.properties.min_value && g.data[1] < this.properties.min_value || -1 != this.properties.max_value && g.data[1] > this.properties.max_value) { - return; - } - } - this.trigger("on_midi", g); - } - }; - w.registerNodeType("midi/filter", g); - D.title = "MIDIEvent"; - D.desc = "Create a MIDI Event"; - D.color = "#243"; - D.prototype.onAction = function(e, g) { - "assign" == e ? (this.properties.channel = g.channel, this.properties.cmd = g.cmd, this.properties.value1 = g.data[1], this.properties.value2 = g.data[2], g.cmd == c.NOTEON ? this.gate = !0 : g.cmd == c.NOTEOFF && (this.gate = !1)) : (g = this.midi_event, g.channel = this.properties.channel, this.properties.cmd && this.properties.cmd.constructor === String ? g.setCommandFromString(this.properties.cmd) : g.cmd = this.properties.cmd, g.data[0] = g.cmd | g.channel, g.data[1] = Number(this.properties.value1), - g.data[2] = Number(this.properties.value2), this.trigger("on_midi", g)); - }; - D.prototype.onExecute = function() { - var e = this.properties; - if (this.inputs) { - for (var g = 0; g < this.inputs.length; ++g) { - var h = this.inputs[g]; - if (-1 != h.link) { - switch(h.name) { - case "note": - h = this.getInputData(g), null != h && (h.constructor === String && (h = c.NoteStringToPitch(h)), this.properties.value1 = (h | 0) % 255); - } - } - } - } - if (this.outputs) { - for (g = 0; g < this.outputs.length; ++g) { - switch(this.outputs[g].name) { - case "midi": - h = new c; - h.setup([e.cmd, e.value1, e.value2]); - h.channel = e.channel; - break; - case "command": - h = e.cmd; - break; - case "cc": - h = e.value1; - break; - case "cc_value": - h = e.value2; - break; - case "note": - h = e.cmd == c.NOTEON || e.cmd == c.NOTEOFF ? e.value1 : null; - break; - case "velocity": - h = e.cmd == c.NOTEON ? e.value2 : null; - break; - case "pitch": - h = e.cmd == c.NOTEON ? c.computePitch(e.value1) : null; - break; - case "pitchbend": - h = e.cmd == c.PITCHBEND ? c.computePitchBend(e.value1, e.value2) : null; - break; - case "gate": - h = this.gate; - break; - default: - continue; - } - null !== h && this.setOutputData(g, h); - } - } - }; - D.prototype.onPropertyChanged = function(e, g) { - "cmd" == e && (this.properties.cmd = c.computeCommandFromString(g)); - }; - D.prototype.onGetInputs = function() { - return [["note", "number"]]; - }; - D.prototype.onGetOutputs = function() { - return [["midi", "midi"], ["on_midi", w.EVENT], ["command", "number"], ["note", "number"], ["velocity", "number"], ["cc", "number"], ["cc_value", "number"], ["pitch", "number"], ["gate", "bool"], ["pitchbend", "number"]]; - }; - w.registerNodeType("midi/event", D); - B.title = "MIDICC"; - B.desc = "gets a Controller Change"; - B.color = "#243"; - B.prototype.onExecute = function() { - k.input && (this.properties.value = k.input.state.cc[this.properties.cc]); - this.setOutputData(0, this.properties.value); - }; - w.registerNodeType("midi/cc", B); - x.title = "MIDI Generator"; - x.desc = "Generates a random MIDI note"; - x.color = "#243"; - x.processScale = function(e) { - e = e.split(","); - for (var g = 0; g < e.length; ++g) { - var l = e[g]; - e[g] = 2 == l.length && "#" != l[1] || 2 < l.length ? -w.MIDIEvent.NoteStringToPitch(l) : c.note_to_index[l] || 0; - } - return e; - }; - x.prototype.onPropertyChanged = function(c, e) { - "notes" == c && (this.notes_pitches = x.processScale(e)); - }; - x.prototype.onExecute = function() { - var c = this.getInputData(2); - null != c && (this.properties.octave = c); - if (c = this.getInputData(1)) { - this.notes_pitches = x.processScale(c); - } - }; - x.prototype.onAction = function(e, g) { - var l = 0; - g = this.notes_pitches.length; - e = 0; - "sequence" == this.properties.mode ? e = this.sequence_index = (this.sequence_index + 1) % g : "random" == this.properties.mode && (e = Math.floor(Math.random() * g)); - g = this.notes_pitches[e]; - l = 0 <= g ? g + 12 * (this.properties.octave - 1) + 33 : -g; - g = new c; - g.setup([c.NOTEON, l, 10]); - e = this.properties.duration || 1; - this.trigger("note", g); - setTimeout(function() { - var f = new c; - f.setup([c.NOTEOFF, l, 0]); - this.trigger("note", f); - }.bind(this), 1000 * e); - }; - w.registerNodeType("midi/generator", x); - E.title = "MIDI Transpose"; - E.desc = "Transpose a MIDI note"; - E.color = "#243"; - E.prototype.onAction = function(e, g) { - g && g.constructor === c && (g.data[0] == c.NOTEON || g.data[0] == c.NOTEOFF ? (this.midi_event = new c, this.midi_event.setup(g.data), this.midi_event.data[1] = Math.round(this.midi_event.data[1] + this.properties.amount), this.trigger("out", this.midi_event)) : this.trigger("out", g)); - }; - E.prototype.onExecute = function() { - var c = this.getInputData(1); - null != c && (this.properties.amount = c); - }; - w.registerNodeType("midi/transpose", E); - A.title = "MIDI Quantize Pitch"; - A.desc = "Transpose a MIDI note tp fit an scale"; - A.color = "#243"; - A.prototype.onPropertyChanged = function(c, e) { - "scale" == c && this.processScale(e); - }; - A.prototype.processScale = function(c) { - this._current_scale = c; - this.notes_pitches = x.processScale(c); - for (c = 0; 12 > c; ++c) { - this.valid_notes[c] = -1 != this.notes_pitches.indexOf(c); - } - for (c = 0; 12 > c; ++c) { - if (this.valid_notes[c]) { - this.offset_notes[c] = 0; - } else { - for (var e = 1; 12 > e; ++e) { - if (this.valid_notes[(c - e) % 12]) { - this.offset_notes[c] = -e; - break; - } - if (this.valid_notes[(c + e) % 12]) { - this.offset_notes[c] = e; - break; - } - } - } - } - }; - A.prototype.onAction = function(e, g) { - g && g.constructor === c && (g.data[0] == c.NOTEON || g.data[0] == c.NOTEOFF ? (this.midi_event = new c, this.midi_event.setup(g.data), this.midi_event.data[1] += this.offset_notes[c.note_to_index[g.note]], this.trigger("out", this.midi_event)) : this.trigger("out", g)); - }; - A.prototype.onExecute = function() { - var c = this.getInputData(1); - null != c && c != this._current_scale && this.processScale(c); - }; - w.registerNodeType("midi/quantize", A); - e.title = "MIDI Play"; - e.desc = "Plays a MIDI note"; - e.color = "#243"; - e.prototype.onAction = function(e, g) { - if (g && g.constructor === c) { - if (this.instrument && g.data[0] == c.NOTEON) { - e = g.note; - if (!e || "undefined" == e || e.constructor !== String) { - return; - } - this.instrument.play(e, g.octave, this.properties.duration, this.properties.volume); - } - this.trigger("note", g); - } - }; - e.prototype.onExecute = function() { - var c = this.getInputData(1); - null != c && (this.properties.volume = c); - c = this.getInputData(2); - null != c && (this.properties.duration = c); - }; - w.registerNodeType("midi/play", e); - y.title = "MIDI Keys"; - y.desc = "Keyboard to play notes"; - y.color = "#243"; - y.keys = [{x:0, w:1, h:1, t:0}, {x:0.75, w:0.5, h:0.6, t:1}, {x:1, w:1, h:1, t:0}, {x:1.75, w:0.5, h:0.6, t:1}, {x:2, w:1, h:1, t:0}, {x:2.75, w:0.5, h:0.6, t:1}, {x:3, w:1, h:1, t:0}, {x:4, w:1, h:1, t:0}, {x:4.75, w:0.5, h:0.6, t:1}, {x:5, w:1, h:1, t:0}, {x:5.75, w:0.5, h:0.6, t:1}, {x:6, w:1, h:1, t:0}]; - y.prototype.onDrawForeground = function(c) { - if (!this.flags.collapsed) { - var e = 12 * this.properties.num_octaves; - this.keys.length = e; - var g = this.size[0] / (7 * this.properties.num_octaves), f = this.size[1]; - c.globalAlpha = 1; - for (var a = 0; 2 > a; a++) { - for (var b = 0; b < e; ++b) { - var d = y.keys[b % 12]; - if (d.t == a) { - var h = 7 * Math.floor(b / 12) * g + d.x * g; - c.fillStyle = 0 == a ? this.keys[b] ? "#CCC" : "white" : this.keys[b] ? "#333" : "black"; - c.fillRect(h + 1, 0, g * d.w - 2, f * d.h); - } - } - } - } - }; - y.prototype.getKeyIndex = function(c) { - for (var e = this.size[0] / (7 * this.properties.num_octaves), g = this.size[1], f = 1; 0 <= f; f--) { - for (var a = 0; a < this.keys.length; ++a) { - var b = y.keys[a % 12]; - if (b.t == f) { - var d = 7 * Math.floor(a / 12) * e + b.x * e, h = e * b.w; - b = g * b.h; - if (!(c[0] < d || c[0] > d + h || c[1] > b)) { - return a; - } - } - } - } - return -1; - }; - y.prototype.onAction = function(e, g) { - if ("reset" == e) { - for (g = 0; g < this.keys.length; ++g) { - this.keys[g] = !1; - } - } else { - g && g.constructor === c && (e = g.data[1] - (12 * (this.properties.start_octave - 1) + 29), 0 <= e && e < this.keys.length && (g.data[0] == c.NOTEON ? this.keys[e] = !0 : g.data[0] == c.NOTEOFF && (this.keys[e] = !1)), this.trigger("note", g)); - } - }; - y.prototype.onMouseDown = function(e, g) { - if (!(0 > g[1])) { - return e = this.getKeyIndex(g), this.keys[e] = !0, this._last_key = e, e = 12 * (this.properties.start_octave - 1) + 29 + e, g = new c, g.setup([c.NOTEON, e, 100]), this.trigger("note", g), !0; - } - }; - y.prototype.onMouseMove = function(e, g) { - if (!(0 > g[1] || -1 == this._last_key)) { - this.setDirtyCanvas(!0); - e = this.getKeyIndex(g); - if (this._last_key == e) { - return !0; - } - this.keys[this._last_key] = !1; - g = 12 * (this.properties.start_octave - 1) + 29 + this._last_key; - var h = new c; - h.setup([c.NOTEOFF, g, 100]); - this.trigger("note", h); - this.keys[e] = !0; - g = 12 * (this.properties.start_octave - 1) + 29 + e; - h = new c; - h.setup([c.NOTEON, g, 100]); - this.trigger("note", h); - this._last_key = e; - return !0; - } - }; - y.prototype.onMouseUp = function(e, g) { - if (!(0 > g[1])) { - return e = this.getKeyIndex(g), this.keys[e] = !1, this._last_key = -1, e = 12 * (this.properties.start_octave - 1) + 29 + e, g = new c, g.setup([c.NOTEOFF, e, 100]), this.trigger("note", g), !0; - } - }; - w.registerNodeType("midi/keys", y); -})(this); -(function(v) { - function c() { - this.properties = {src:"", gain:0.5, loop:!0, autoplay:!0, playbackRate:1}; - this._loading_audio = !1; - this._audiobuffer = null; - this._audionodes = []; - this._last_sourcenode = null; - this.addOutput("out", "audio"); - this.addInput("gain", "number"); - this.audionode = r.getAudioContext().createGain(); - this.audionode.graphnode = this; - this.audionode.gain.value = this.properties.gain; - this.properties.src && this.loadSound(this.properties.src); - } - function k() { - this.properties = {gain:0.5}; - this._audionodes = []; - this._media_stream = null; - this.addOutput("out", "audio"); - this.addInput("gain", "number"); - this.audionode = r.getAudioContext().createGain(); - this.audionode.graphnode = this; - this.audionode.gain.value = this.properties.gain; - } - function h() { - this.properties = {fftSize:2048, minDecibels:-100, maxDecibels:-10, smoothingTimeConstant:0.5}; - this.audionode = r.getAudioContext().createAnalyser(); - this.audionode.graphnode = this; - this.audionode.fftSize = this.properties.fftSize; - this.audionode.minDecibels = this.properties.minDecibels; - this.audionode.maxDecibels = this.properties.maxDecibels; - this.audionode.smoothingTimeConstant = this.properties.smoothingTimeConstant; - this.addInput("in", "audio"); - this.addOutput("freqs", "array"); - this.addOutput("samples", "array"); - this._time_bin = this._freq_bin = null; - } - function m() { - this.properties = {gain:1}; - this.audionode = r.getAudioContext().createGain(); - this.addInput("in", "audio"); - this.addInput("gain", "number"); - this.addOutput("out", "audio"); - } - function t() { - this.properties = {impulse_src:"", normalize:!0}; - this.audionode = r.getAudioContext().createConvolver(); - this.addInput("in", "audio"); - this.addOutput("out", "audio"); - } - function g() { - this.properties = {threshold:-50, knee:40, ratio:12, reduction:-20, attack:0, release:0.25}; - this.audionode = r.getAudioContext().createDynamicsCompressor(); - this.addInput("in", "audio"); - this.addOutput("out", "audio"); - } - function D() { - this.properties = {}; - this.audionode = r.getAudioContext().createWaveShaper(); - this.addInput("in", "audio"); - this.addInput("shape", "waveshape"); - this.addOutput("out", "audio"); - } - function B() { - this.properties = {gain1:0.5, gain2:0.5}; - this.audionode = r.getAudioContext().createGain(); - this.audionode1 = r.getAudioContext().createGain(); - this.audionode1.gain.value = this.properties.gain1; - this.audionode2 = r.getAudioContext().createGain(); - this.audionode2.gain.value = this.properties.gain2; - this.audionode1.connect(this.audionode); - this.audionode2.connect(this.audionode); - this.addInput("in1", "audio"); - this.addInput("in1 gain", "number"); - this.addInput("in2", "audio"); - this.addInput("in2 gain", "number"); - this.addOutput("out", "audio"); - } - function x() { - this.properties = {A:0.1, D:0.1, S:0.1, R:0.1}; - this.audionode = r.getAudioContext().createGain(); - this.audionode.gain.value = 0; - this.addInput("in", "audio"); - this.addInput("gate", "bool"); - this.addOutput("out", "audio"); - this.gate = !1; - } - function E() { - this.properties = {delayTime:0.5}; - this.audionode = r.getAudioContext().createDelay(10); - this.audionode.delayTime.value = this.properties.delayTime; - this.addInput("in", "audio"); - this.addInput("time", "number"); - this.addOutput("out", "audio"); - } - function A() { - this.properties = {frequency:350, detune:0, Q:1}; - this.addProperty("type", "lowpass", "enum", {values:"lowpass highpass bandpass lowshelf highshelf peaking notch allpass".split(" ")}); - this.audionode = r.getAudioContext().createBiquadFilter(); - this.addInput("in", "audio"); - this.addOutput("out", "audio"); - } - function e() { - this.properties = {frequency:440, detune:0, type:"sine"}; - this.addProperty("type", "sine", "enum", {values:["sine", "square", "sawtooth", "triangle", "custom"]}); - this.audionode = r.getAudioContext().createOscillator(); - this.addOutput("out", "audio"); - } - function y() { - this.properties = {continuous:!0, mark:-1}; - this.addInput("data", "array"); - this.addInput("mark", "number"); - this.size = [300, 200]; - this._last_buffer = null; - } - function w() { - this.properties = {band:440, amplitude:1}; - this.addInput("freqs", "array"); - this.addOutput("signal", "number"); - } - function z() { - if (!z.default_code) { - var c = z.default_function.toString(), a = c.indexOf("{") + 1, b = c.lastIndexOf("}"); - z.default_code = c.substr(a, b - a); - } - this.properties = {code:z.default_code}; - c = r.getAudioContext(); - c.createScriptProcessor ? this.audionode = c.createScriptProcessor(4096, 1, 1) : (console.warn("ScriptProcessorNode deprecated"), this.audionode = c.createGain()); - this.processCode(); - z._bypass_function || (z._bypass_function = this.audionode.onaudioprocess); - this.addInput("in", "audio"); - this.addOutput("out", "audio"); - } - function l() { - this.audionode = r.getAudioContext().destination; - this.addInput("in", "audio"); - } - var p = v.LiteGraph, r = {}; - v.LGAudio = r; - r.getAudioContext = function() { - if (!this._audio_context) { - window.AudioContext = window.AudioContext || window.webkitAudioContext; - if (!window.AudioContext) { - return console.error("AudioContext not supported by browser"), null; - } - this._audio_context = new AudioContext; - this._audio_context.onmessage = function(c) { - console.log("msg", c); - }; - this._audio_context.onended = function(c) { - console.log("ended", c); - }; - this._audio_context.oncomplete = function(c) { - console.log("complete", c); - }; - } - return this._audio_context; - }; - r.connect = function(c, a) { - try { - c.connect(a); - } catch (b) { - console.warn("LGraphAudio:", b); - } - }; - r.disconnect = function(c, a) { - try { - c.disconnect(a); - } catch (b) { - console.warn("LGraphAudio:", b); - } - }; - r.changeAllAudiosConnections = function(c, a) { - if (c.inputs) { - for (var b = 0; b < c.inputs.length; ++b) { - var d = c.graph.links[c.inputs[b].link]; - if (d) { - var e = c.graph.getNodeById(d.origin_id); - e = e.getAudioNodeInOutputSlot ? e.getAudioNodeInOutputSlot(d.origin_slot) : e.audionode; - d = c.getAudioNodeInInputSlot ? c.getAudioNodeInInputSlot(b) : c.audionode; - a ? r.connect(e, d) : r.disconnect(e, d); - } - } - } - if (c.outputs) { - for (b = 0; b < c.outputs.length; ++b) { - for (var f = c.outputs[b], g = 0; g < f.links.length; ++g) { - if (d = c.graph.links[f.links[g]]) { - e = c.getAudioNodeInOutputSlot ? c.getAudioNodeInOutputSlot(b) : c.audionode; - var h = c.graph.getNodeById(d.target_id); - d = h.getAudioNodeInInputSlot ? h.getAudioNodeInInputSlot(d.target_slot) : h.audionode; - a ? r.connect(e, d) : r.disconnect(e, d); - } - } - } - } - }; - r.onConnectionsChange = function(c, a, b, d) { - c == p.OUTPUT && (c = null, d && (c = this.graph.getNodeById(d.target_id)), c && (a = this.getAudioNodeInOutputSlot ? this.getAudioNodeInOutputSlot(a) : this.audionode, d = c.getAudioNodeInInputSlot ? c.getAudioNodeInInputSlot(d.target_slot) : c.audionode, b ? r.connect(a, d) : r.disconnect(a, d))); - }; - r.createAudioNodeWrapper = function(c) { - var a = c.prototype.onPropertyChanged; - c.prototype.onPropertyChanged = function(b, c) { - a && a.call(this, b, c); - this.audionode && void 0 !== this.audionode[b] && (void 0 !== this.audionode[b].value ? this.audionode[b].value = c : this.audionode[b] = c); - }; - c.prototype.onConnectionsChange = r.onConnectionsChange; - }; - r.cached_audios = {}; - r.loadSound = function(c, a, b) { - function d(a) { - console.log("Audio loading sample error:", a); - b && b(a); - } - if (r.cached_audios[c] && -1 == c.indexOf("blob:")) { - a && a(r.cached_audios[c]); - } else { - r.onProcessAudioURL && (c = r.onProcessAudioURL(c)); - var e = new XMLHttpRequest; - e.open("GET", c, !0); - e.responseType = "arraybuffer"; - var f = r.getAudioContext(); - e.onload = function() { - console.log("AudioSource loaded"); - f.decodeAudioData(e.response, function(b) { - console.log("AudioSource decoded"); - r.cached_audios[c] = b; - a && a(b); - }, d); - }; - e.send(); - return e; - } - }; - c["@src"] = {widget:"resource"}; - c.supported_extensions = ["wav", "ogg", "mp3"]; - c.prototype.onAdded = function(c) { - if (c.status === LGraph.STATUS_RUNNING) { - this.onStart(); - } - }; - c.prototype.onStart = function() { - this._audiobuffer && this.properties.autoplay && this.playBuffer(this._audiobuffer); - }; - c.prototype.onStop = function() { - this.stopAllSounds(); - }; - c.prototype.onPause = function() { - this.pauseAllSounds(); - }; - c.prototype.onUnpause = function() { - this.unpauseAllSounds(); - }; - c.prototype.onRemoved = function() { - this.stopAllSounds(); - this._dropped_url && URL.revokeObjectURL(this._url); - }; - c.prototype.stopAllSounds = function() { - for (var c = 0; c < this._audionodes.length; ++c) { - this._audionodes[c].started && (this._audionodes[c].started = !1, this._audionodes[c].stop()); - } - this._audionodes.length = 0; - }; - c.prototype.pauseAllSounds = function() { - r.getAudioContext().suspend(); - }; - c.prototype.unpauseAllSounds = function() { - r.getAudioContext().resume(); - }; - c.prototype.onExecute = function() { - if (this.inputs) { - for (var c = 0; c < this.inputs.length; ++c) { - var a = this.inputs[c]; - if (null != a.link) { - var b = this.getInputData(c); - if (void 0 !== b) { - if ("gain" == a.name) { - this.audionode.gain.value = b; - } else { - if ("playbackRate" == a.name) { - for (this.properties.playbackRate = b, a = 0; a < this._audionodes.length; ++a) { - this._audionodes[a].playbackRate.value = b; - } - } - } - } - } - } - } - if (this.outputs) { - for (c = 0; c < this.outputs.length; ++c) { - "buffer" == this.outputs[c].name && this._audiobuffer && this.setOutputData(c, this._audiobuffer); - } - } - }; - c.prototype.onAction = function(c) { - this._audiobuffer && ("Play" == c ? this.playBuffer(this._audiobuffer) : "Stop" == c && this.stopAllSounds()); - }; - c.prototype.onPropertyChanged = function(c, a) { - if ("src" == c) { - this.loadSound(a); - } else { - if ("gain" == c) { - this.audionode.gain.value = a; - } else { - if ("playbackRate" == c) { - for (c = 0; c < this._audionodes.length; ++c) { - this._audionodes[c].playbackRate.value = a; - } - } - } - } - }; - c.prototype.playBuffer = function(c) { - var a = this, b = r.getAudioContext().createBufferSource(); - this._last_sourcenode = b; - b.graphnode = this; - b.buffer = c; - b.loop = this.properties.loop; - b.playbackRate.value = this.properties.playbackRate; - this._audionodes.push(b); - b.connect(this.audionode); - this._audionodes.push(b); - b.onended = function() { - a.trigger("ended"); - var c = a._audionodes.indexOf(b); - -1 != c && a._audionodes.splice(c, 1); - }; - b.started || (b.started = !0, b.start()); - return b; - }; - c.prototype.loadSound = function(c) { - var a = this; - this._request && (this._request.abort(), this._request = null); - this._audiobuffer = null; - this._loading_audio = !1; - c && (this._request = r.loadSound(c, function(b) { - this.boxcolor = p.NODE_DEFAULT_BOXCOLOR; - a._audiobuffer = b; - a._loading_audio = !1; - if (a.graph && a.graph.status === LGraph.STATUS_RUNNING) { - a.onStart(); - } - }), this._loading_audio = !0, this.boxcolor = "#AA4"); - }; - c.prototype.onConnectionsChange = r.onConnectionsChange; - c.prototype.onGetInputs = function() { - return [["playbackRate", "number"], ["Play", p.ACTION], ["Stop", p.ACTION]]; - }; - c.prototype.onGetOutputs = function() { - return [["buffer", "audiobuffer"], ["ended", p.EVENT]]; - }; - c.prototype.onDropFile = function(c) { - this._dropped_url && URL.revokeObjectURL(this._dropped_url); - c = URL.createObjectURL(c); - this.properties.src = c; - this.loadSound(c); - this._dropped_url = c; - }; - c.title = "Source"; - c.desc = "Plays audio"; - p.registerNodeType("audio/source", c); - k.prototype.onAdded = function(c) { - if (c.status === LGraph.STATUS_RUNNING) { - this.onStart(); - } - }; - k.prototype.onStart = function() { - null != this._media_stream || this._waiting_confirmation || this.openStream(); - }; - k.prototype.onStop = function() { - this.audionode.gain.value = 0; - }; - k.prototype.onPause = function() { - this.audionode.gain.value = 0; - }; - k.prototype.onUnpause = function() { - this.audionode.gain.value = this.properties.gain; - }; - k.prototype.onRemoved = function() { - this.audionode.gain.value = 0; - this.audiosource_node && (this.audiosource_node.disconnect(this.audionode), this.audiosource_node = null); - if (this._media_stream) { - var c = this._media_stream.getTracks(); - c.length && c[0].stop(); - } - }; - k.prototype.openStream = function() { - if (navigator.mediaDevices) { - this._waiting_confirmation = !0; - navigator.mediaDevices.getUserMedia({audio:!0, video:!1}).then(this.streamReady.bind(this)).catch(function(a) { - console.log("Media rejected", a); - c._media_stream = !1; - c.boxcolor = "red"; - }); - var c = this; - } else { - console.log("getUserMedia() is not supported in your browser, use chrome and enable WebRTC from about://flags"); - } - }; - k.prototype.streamReady = function(c) { - this._media_stream = c; - this.audiosource_node && this.audiosource_node.disconnect(this.audionode); - this.audiosource_node = r.getAudioContext().createMediaStreamSource(c); - this.audiosource_node.graphnode = this; - this.audiosource_node.connect(this.audionode); - this.boxcolor = "white"; - }; - k.prototype.onExecute = function() { - null != this._media_stream || this._waiting_confirmation || this.openStream(); - if (this.inputs) { - for (var c = 0; c < this.inputs.length; ++c) { - var a = this.inputs[c]; - if (null != a.link) { - var b = this.getInputData(c); - void 0 !== b && "gain" == a.name && (this.audionode.gain.value = this.properties.gain = b); - } - } - } - }; - k.prototype.onAction = function(c) { - "Play" == c ? this.audionode.gain.value = this.properties.gain : "Stop" == c && (this.audionode.gain.value = 0); - }; - k.prototype.onPropertyChanged = function(c, a) { - "gain" == c && (this.audionode.gain.value = a); - }; - k.prototype.onConnectionsChange = r.onConnectionsChange; - k.prototype.onGetInputs = function() { - return [["playbackRate", "number"], ["Play", p.ACTION], ["Stop", p.ACTION]]; - }; - k.title = "MediaSource"; - k.desc = "Plays microphone"; - p.registerNodeType("audio/media_source", k); - h.prototype.onPropertyChanged = function(c, a) { - this.audionode[c] = a; - }; - h.prototype.onExecute = function() { - if (this.isOutputConnected(0)) { - var c = this.audionode.frequencyBinCount; - this._freq_bin && this._freq_bin.length == c || (this._freq_bin = new Uint8Array(c)); - this.audionode.getByteFrequencyData(this._freq_bin); - this.setOutputData(0, this._freq_bin); - } - this.isOutputConnected(1) && (c = this.audionode.frequencyBinCount, this._time_bin && this._time_bin.length == c || (this._time_bin = new Uint8Array(c)), this.audionode.getByteTimeDomainData(this._time_bin), this.setOutputData(1, this._time_bin)); - for (c = 1; c < this.inputs.length; ++c) { - var a = this.inputs[c]; - if (null != a.link) { - var b = this.getInputData(c); - void 0 !== b && (this.audionode[a.name].value = b); - } - } - }; - h.prototype.onGetInputs = function() { - return [["minDecibels", "number"], ["maxDecibels", "number"], ["smoothingTimeConstant", "number"]]; - }; - h.prototype.onGetOutputs = function() { - return [["freqs", "array"], ["samples", "array"]]; - }; - h.title = "Analyser"; - h.desc = "Audio Analyser"; - p.registerNodeType("audio/analyser", h); - m.prototype.onExecute = function() { - if (this.inputs && this.inputs.length) { - for (var c = 1; c < this.inputs.length; ++c) { - var a = this.inputs[c], b = this.getInputData(c); - void 0 !== b && (this.audionode[a.name].value = b); - } - } - }; - r.createAudioNodeWrapper(m); - m.title = "Gain"; - m.desc = "Audio gain"; - p.registerNodeType("audio/gain", m); - r.createAudioNodeWrapper(t); - t.prototype.onRemove = function() { - this._dropped_url && URL.revokeObjectURL(this._dropped_url); - }; - t.prototype.onPropertyChanged = function(c, a) { - "impulse_src" == c ? this.loadImpulse(a) : "normalize" == c && (this.audionode.normalize = a); - }; - t.prototype.onDropFile = function(c) { - this._dropped_url && URL.revokeObjectURL(this._dropped_url); - this._dropped_url = URL.createObjectURL(c); - this.properties.impulse_src = this._dropped_url; - this.loadImpulse(this._dropped_url); - }; - t.prototype.loadImpulse = function(c) { - var a = this; - this._request && (this._request.abort(), this._request = null); - this._impulse_buffer = null; - this._loading_impulse = !1; - c && (this._request = r.loadSound(c, function(b) { - a._impulse_buffer = b; - a.audionode.buffer = b; - console.log("Impulse signal set"); - a._loading_impulse = !1; - }), this._loading_impulse = !0); - }; - t.title = "Convolver"; - t.desc = "Convolves the signal (used for reverb)"; - p.registerNodeType("audio/convolver", t); - r.createAudioNodeWrapper(g); - g.prototype.onExecute = function() { - if (this.inputs && this.inputs.length) { - for (var c = 1; c < this.inputs.length; ++c) { - var a = this.inputs[c]; - if (null != a.link) { - var b = this.getInputData(c); - void 0 !== b && (this.audionode[a.name].value = b); - } - } - } - }; - g.prototype.onGetInputs = function() { - return [["threshold", "number"], ["knee", "number"], ["ratio", "number"], ["reduction", "number"], ["attack", "number"], ["release", "number"]]; - }; - g.title = "DynamicsCompressor"; - g.desc = "Dynamics Compressor"; - p.registerNodeType("audio/dynamicsCompressor", g); - D.prototype.onExecute = function() { - if (this.inputs && this.inputs.length) { - var c = this.getInputData(1); - void 0 !== c && (this.audionode.curve = c); - } - }; - D.prototype.setWaveShape = function(c) { - this.audionode.curve = c; - }; - r.createAudioNodeWrapper(D); - B.prototype.getAudioNodeInInputSlot = function(c) { - if (0 == c) { - return this.audionode1; - } - if (2 == c) { - return this.audionode2; - } - }; - B.prototype.onPropertyChanged = function(c, a) { - "gain1" == c ? this.audionode1.gain.value = a : "gain2" == c && (this.audionode2.gain.value = a); - }; - B.prototype.onExecute = function() { - if (this.inputs && this.inputs.length) { - for (var c = 1; c < this.inputs.length; ++c) { - var a = this.inputs[c]; - null != a.link && "audio" != a.type && (a = this.getInputData(c), void 0 !== a && (1 == c ? this.audionode1.gain.value = a : 3 == c && (this.audionode2.gain.value = a))); - } - } - }; - r.createAudioNodeWrapper(B); - B.title = "Mixer"; - B.desc = "Audio mixer"; - p.registerNodeType("audio/mixer", B); - x.prototype.onExecute = function() { - var c = r.getAudioContext().currentTime, a = this.audionode.gain, b = this.getInputData(1), d = this.getInputOrProperty("A"), e = this.getInputOrProperty("D"), g = this.getInputOrProperty("S"), h = this.getInputOrProperty("R"); - !this.gate && b ? (a.cancelScheduledValues(0), a.setValueAtTime(0, c), a.linearRampToValueAtTime(1, c + d), a.linearRampToValueAtTime(g, c + d + e)) : this.gate && !b && (a.cancelScheduledValues(0), a.setValueAtTime(a.value, c), a.linearRampToValueAtTime(0, c + h)); - this.gate = b; - }; - x.prototype.onGetInputs = function() { - return [["A", "number"], ["D", "number"], ["S", "number"], ["R", "number"]]; - }; - r.createAudioNodeWrapper(x); - x.title = "ADSR"; - x.desc = "Audio envelope"; - p.registerNodeType("audio/adsr", x); - r.createAudioNodeWrapper(E); - E.prototype.onExecute = function() { - var c = this.getInputData(1); - void 0 !== c && (this.audionode.delayTime.value = c); - }; - E.title = "Delay"; - E.desc = "Audio delay"; - p.registerNodeType("audio/delay", E); - A.prototype.onExecute = function() { - if (this.inputs && this.inputs.length) { - for (var c = 1; c < this.inputs.length; ++c) { - var a = this.inputs[c]; - if (null != a.link) { - var b = this.getInputData(c); - void 0 !== b && (this.audionode[a.name].value = b); - } - } - } - }; - A.prototype.onGetInputs = function() { - return [["frequency", "number"], ["detune", "number"], ["Q", "number"]]; - }; - r.createAudioNodeWrapper(A); - A.title = "BiquadFilter"; - A.desc = "Audio filter"; - p.registerNodeType("audio/biquadfilter", A); - e.prototype.onStart = function() { - if (!this.audionode.started) { - this.audionode.started = !0; - try { - this.audionode.start(); - } catch (f) { - } - } - }; - e.prototype.onStop = function() { - this.audionode.started && (this.audionode.started = !1, this.audionode.stop()); - }; - e.prototype.onPause = function() { - this.onStop(); - }; - e.prototype.onUnpause = function() { - this.onStart(); - }; - e.prototype.onExecute = function() { - if (this.inputs && this.inputs.length) { - for (var c = 0; c < this.inputs.length; ++c) { - var a = this.inputs[c]; - if (null != a.link) { - var b = this.getInputData(c); - void 0 !== b && (this.audionode[a.name].value = b); - } - } - } - }; - e.prototype.onGetInputs = function() { - return [["frequency", "number"], ["detune", "number"], ["type", "string"]]; - }; - r.createAudioNodeWrapper(e); - e.title = "Oscillator"; - e.desc = "Oscillator"; - p.registerNodeType("audio/oscillator", e); - y.prototype.onExecute = function() { - this._last_buffer = this.getInputData(0); - var c = this.getInputData(1); - void 0 !== c && (this.properties.mark = c); - this.setDirtyCanvas(!0, !1); - }; - y.prototype.onDrawForeground = function(c) { - if (this._last_buffer) { - var a = this._last_buffer, b = a.length / this.size[0], d = this.size[1]; - c.fillStyle = "black"; - c.fillRect(0, 0, this.size[0], this.size[1]); - c.strokeStyle = "white"; - c.beginPath(); - var e = 0; - if (this.properties.continuous) { - c.moveTo(e, d); - for (var f = 0; f < a.length; f += b) { - c.lineTo(e, d - a[f | 0] / 255 * d), e++; - } - } else { - for (f = 0; f < a.length; f += b) { - c.moveTo(e + 0.5, d), c.lineTo(e + 0.5, d - a[f | 0] / 255 * d), e++; - } - } - c.stroke(); - 0 <= this.properties.mark && (a = r.getAudioContext().sampleRate / a.length, e = this.properties.mark / a * 2 / b, e >= this.size[0] && (e = this.size[0] - 1), c.strokeStyle = "red", c.beginPath(), c.moveTo(e, d), c.lineTo(e, 0), c.stroke()); - } - }; - y.title = "Visualization"; - y.desc = "Audio Visualization"; - p.registerNodeType("audio/visualization", y); - w.prototype.onExecute = function() { - if (this._freqs = this.getInputData(0)) { - var c = this.properties.band, a = this.getInputData(1); - void 0 !== a && (c = a); - a = r.getAudioContext().sampleRate / this._freqs.length; - a = c / a * 2; - a >= this._freqs.length ? a = this._freqs[this._freqs.length - 1] : (c = a | 0, a -= c, a = this._freqs[c] * (1 - a) + this._freqs[c + 1] * a); - this.setOutputData(0, a / 255 * this.properties.amplitude); - } - }; - w.prototype.onGetInputs = function() { - return [["band", "number"]]; - }; - w.title = "Signal"; - w.desc = "extract the signal of some frequency"; - p.registerNodeType("audio/signal", w); - z.prototype.onAdded = function(c) { - c.status == LGraph.STATUS_RUNNING && (this.audionode.onaudioprocess = this._callback); - }; - z["@code"] = {widget:"code"}; - z.prototype.onStart = function() { - this.audionode.onaudioprocess = this._callback; - }; - z.prototype.onStop = function() { - this.audionode.onaudioprocess = z._bypass_function; - }; - z.prototype.onPause = function() { - this.audionode.onaudioprocess = z._bypass_function; - }; - z.prototype.onUnpause = function() { - this.audionode.onaudioprocess = this._callback; - }; - z.prototype.onExecute = function() { - }; - z.prototype.onRemoved = function() { - this.audionode.onaudioprocess = z._bypass_function; - }; - z.prototype.processCode = function() { - try { - this._script = new (new Function("properties", this.properties.code))(this.properties), this._old_code = this.properties.code, this._callback = this._script.onaudioprocess; - } catch (f) { - console.error("Error in onaudioprocess code", f), this._callback = z._bypass_function, this.audionode.onaudioprocess = this._callback; - } - }; - z.prototype.onPropertyChanged = function(c, a) { - "code" == c && (this.properties.code = a, this.processCode(), this.graph && this.graph.status == LGraph.STATUS_RUNNING && (this.audionode.onaudioprocess = this._callback)); - }; - z.default_function = function() { - this.onaudioprocess = function(c) { - var a = c.inputBuffer; - c = c.outputBuffer; - for (var b = 0; b < c.numberOfChannels; b++) { - for (var d = a.getChannelData(b), e = c.getChannelData(b), f = 0; f < a.length; f++) { - e[f] = d[f]; - } - } - }; - }; - r.createAudioNodeWrapper(z); - z.title = "Script"; - z.desc = "apply script to signal"; - p.registerNodeType("audio/script", z); - l.title = "Destination"; - l.desc = "Audio output"; - p.registerNodeType("audio/destination", l); -})(this); -(function(v) { - function c() { - this.size = [60, 20]; - this.addInput("send", h.ACTION); - this.addOutput("received", h.EVENT); - this.addInput("in", 0); - this.addOutput("out", 0); - this.properties = {url:"", room:"lgraph", only_send_changes:!0}; - this._ws = null; - this._last_sent_data = []; - this._last_received_data = []; - } - function k() { - this.room_widget = this.addWidget("text", "Room", "lgraph", this.setRoom.bind(this)); - this.addWidget("button", "Reconnect", null, this.connectSocket.bind(this)); - this.addInput("send", h.ACTION); - this.addOutput("received", h.EVENT); - this.addInput("in", 0); - this.addOutput("out", 0); - this.properties = {url:"tamats.com:55000", room:"lgraph", only_send_changes:!0}; - this._server = null; - this.connectSocket(); - this._last_sent_data = []; - this._last_received_data = []; - "undefined" == typeof SillyClient && console.warn("remember to add SillyClient.js to your project: https://tamats.com/projects/sillyserver/src/sillyclient.js"); - } - var h = v.LiteGraph; - c.title = "WebSocket"; - c.desc = "Send data through a websocket"; - c.prototype.onPropertyChanged = function(c, h) { - "url" == c && this.connectSocket(); - }; - c.prototype.onExecute = function() { - !this._ws && this.properties.url && this.connectSocket(); - if (this._ws && this._ws.readyState == WebSocket.OPEN) { - for (var c = this.properties.room, h = this.properties.only_send_changes, g = 1; g < this.inputs.length; ++g) { - var k = this.getInputData(g); - if (null != k) { - try { - var v = JSON.stringify({type:0, room:c, channel:g, data:k}); - } catch (x) { - continue; - } - h && this._last_sent_data[g] == v || (this._last_sent_data[g] = v, this._ws.send(v)); - } - } - for (g = 1; g < this.outputs.length; ++g) { - this.setOutputData(g, this._last_received_data[g]); - } - "#AFA" == this.boxcolor && (this.boxcolor = "#6C6"); - } - }; - c.prototype.connectSocket = function() { - var c = this, k = this.properties.url; - "ws" != k.substr(0, 2) && (k = "ws://" + k); - this._ws = new WebSocket(k); - this._ws.onopen = function() { - console.log("ready"); - c.boxcolor = "#6C6"; - }; - this._ws.onmessage = function(g) { - c.boxcolor = "#AFA"; - var m = JSON.parse(g.data); - if (!m.room || m.room == this.properties.room) { - if (1 == g.data.type) { - if (m.data.object_class && h[m.data.object_class]) { - g = null; - try { - g = new h[m.data.object_class](m.data), c.triggerSlot(0, g); - } catch (B) { - } - } else { - c.triggerSlot(0, m.data); - } - } else { - c._last_received_data[g.data.channel || 0] = m.data; - } - } - }; - this._ws.onerror = function(g) { - console.log("couldnt connect to websocket"); - c.boxcolor = "#E88"; - }; - this._ws.onclose = function(g) { - console.log("connection closed"); - c.boxcolor = "#000"; - }; - }; - c.prototype.send = function(c) { - this._ws && this._ws.readyState == WebSocket.OPEN && this._ws.send(JSON.stringify({type:1, msg:c})); - }; - c.prototype.onAction = function(c, h) { - this._ws && this._ws.readyState == WebSocket.OPEN && this._ws.send({type:1, room:this.properties.room, action:c, data:h}); - }; - c.prototype.onGetInputs = function() { - return [["in", 0]]; - }; - c.prototype.onGetOutputs = function() { - return [["out", 0]]; - }; - h.registerNodeType("network/websocket", c); - k.title = "SillyClient"; - k.desc = "Connects to SillyServer to broadcast messages"; - k.prototype.onPropertyChanged = function(c, h) { - "room" == c && (this.room_widget.value = h); - this.connectSocket(); - }; - k.prototype.setRoom = function(c) { - this.properties.room = c; - this.room_widget.value = c; - this.connectSocket(); - }; - k.prototype.onDrawForeground = function() { - for (var c = 1; c < this.inputs.length; ++c) { - var h = this.inputs[c]; - h.label = "in_" + c; - } - for (c = 1; c < this.outputs.length; ++c) { - h = this.outputs[c], h.label = "out_" + c; - } - }; - k.prototype.onExecute = function() { - if (this._server && this._server.is_connected) { - for (var c = this.properties.only_send_changes, h = 1; h < this.inputs.length; ++h) { - var g = this.getInputData(h), k = this._last_sent_data[h]; - if (null != g) { - if (c) { - var v = !0; - if (g && g.length && k && k.length == g.length && g.constructor !== String) { - for (var x = 0; x < g.length; ++x) { - if (k[x] != g[x]) { - v = !1; - break; - } - } - } else { - this._last_sent_data[h] != g && (v = !1); - } - if (v) { - continue; - } - } - this._server.sendMessage({type:0, channel:h, data:g}); - if (g.length && g.constructor !== String) { - if (this._last_sent_data[h]) { - for (this._last_sent_data[h].length = g.length, x = 0; x < g.length; ++x) { - this._last_sent_data[h][x] = g[x]; - } - } else { - this._last_sent_data[h] = g.constructor === Array ? g.concat() : new g.constructor(g); - } - } else { - this._last_sent_data[h] = g; - } - } - } - for (h = 1; h < this.outputs.length; ++h) { - this.setOutputData(h, this._last_received_data[h]); - } - "#AFA" == this.boxcolor && (this.boxcolor = "#6C6"); - } - }; - k.prototype.connectSocket = function() { - var c = this; - if ("undefined" == typeof SillyClient) { - this._error || console.error("SillyClient node cannot be used, you must include SillyServer.js"), this._error = !0; - } else { - if (this._server = new SillyClient, this._server.on_ready = function() { - console.log("ready"); - c.boxcolor = "#6C6"; - }, this._server.on_message = function(k, g) { - k = null; - try { - k = JSON.parse(g); - } catch (D) { - return; - } - if (1 == k.type) { - if (k.data.object_class && h[k.data.object_class]) { - g = null; - try { - g = new h[k.data.object_class](k.data), c.triggerSlot(0, g); - } catch (D) { - return; - } - } else { - c.triggerSlot(0, k.data); - } - } else { - c._last_received_data[k.channel || 0] = k.data; - } - c.boxcolor = "#AFA"; - }, this._server.on_error = function(h) { - console.log("couldnt connect to websocket"); - c.boxcolor = "#E88"; - }, this._server.on_close = function(h) { - console.log("connection closed"); - c.boxcolor = "#000"; - }, this.properties.url && this.properties.room) { - try { - this._server.connect(this.properties.url, this.properties.room); - } catch (t) { - console.error("SillyServer error: " + t); - this._server = null; - return; - } - this._final_url = this.properties.url + "/" + this.properties.room; - } - } - }; - k.prototype.send = function(c) { - this._server && this._server.is_connected && this._server.sendMessage({type:1, data:c}); - }; - k.prototype.onAction = function(c, h) { - this._server && this._server.is_connected && this._server.sendMessage({type:1, action:c, data:h}); - }; - k.prototype.onGetInputs = function() { - return [["in", 0]]; - }; - k.prototype.onGetOutputs = function() { - return [["out", 0]]; - }; - h.registerNodeType("network/sillyclient", k); -})(this); - diff --git a/css/litegraph-editor.css b/css/litegraph-editor.css index 7d4c6cf60..4b1239602 100755 --- a/css/litegraph-editor.css +++ b/css/litegraph-editor.css @@ -93,7 +93,7 @@ /* BUTTONS **********************/ -.litegraph-editor button { +.litegraph-editor .btn { /*font-family: "Metro Light";*/ color: #ccc; font-size: 20px; @@ -181,7 +181,7 @@ display: inline-block; width: 90px; height: 15px; - background-image: url("../demo/imgs/load-progress-empty.png"); + background-image: url("../editor/imgs/load-progress-empty.png"); } .litegraph-editor .cpuload .fgload, @@ -190,39 +190,24 @@ width: 4px; height: 15px; max-width: 90px; - background-image: url("../demo/imgs/load-progress-full.png"); + background-image: url("../editor/imgs/load-progress-full.png"); } -.litegraph-editor .dialog { - position: absolute; - top: 50%; - left: 50%; - margin-top: -150px; - margin-left: -200px; - - background-color: #151515; - - min-width: 400px; - min-height: 300px; - box-shadow: 0 0 2px black; +.litegraph-editor textarea.code, .litegraph-editor div.code { + height: 100%; + width: 100%; + background-color: black; + padding: 4px; + font: 16px monospace; + overflow: auto; + resize: none; + outline: none; } -.litegraph-editor .dialog .dialog-header, -.litegraph-editor .dialog .dialog-footer { - height: 40px; +.litegraph-editor .codeflask { + background-color: #2a2a2a; } -.litegraph-editor .dialog .dialog-header .dialog-title { - font: 20px "Arial"; - margin: 4px; - padding: 4px 10px; - display: inline-block; -} - -.litegraph-editor .dialog .dialog-content { - height: calc(100% - 40px); - width: calc(100% - 10px); - background-color: black; - margin: 4px; - display: inline-block; -} +.litegraph-editor .codeflask textarea { + opacity: 0; +} \ No newline at end of file diff --git a/css/litegraph.css b/css/litegraph.css index 428ef008e..80a203ad4 100755 --- a/css/litegraph.css +++ b/css/litegraph.css @@ -5,6 +5,7 @@ user-select: none; -moz-user-select: none; -webkit-user-select: none; + outline: none; } .litegraph.litecontextmenu { @@ -207,6 +208,233 @@ color: black; } +/* DIALOGs ******/ + +.litegraph .dialog { + position: absolute; + top: 50%; + left: 50%; + margin-top: -150px; + margin-left: -200px; + + background-color: #2A2A2A; + + min-width: 400px; + min-height: 200px; + box-shadow: 0 0 4px #111; + border-radius: 6px; +} + +.litegraph .dialog.settings { + left: 10px; + top: 10px; + height: calc( 100% - 20px ); + margin: auto; +} + +.litegraph .dialog .close { + float: right; + margin: 4px; + margin-right: 10px; + cursor: pointer; + font-size: 1.4em; +} + +.litegraph .dialog .close:hover { + color: white; +} + +.litegraph .dialog .dialog-header { + color: #AAA; + border-bottom: 1px solid #161616; +} + +.litegraph .dialog .dialog-header { height: 40px; } +.litegraph .dialog .dialog-footer { height: 50px; padding: 10px; border-top: 1px solid #1a1a1a;} + +.litegraph .dialog .dialog-header .dialog-title { + font: 20px "Arial"; + margin: 4px; + padding: 4px 10px; + display: inline-block; +} + +.litegraph .dialog .dialog-content { + height: calc(100% - 90px); + width: 100%; + min-height: 100px; + display: inline-block; + color: #AAA; + /*background-color: black;*/ +} + +.litegraph .dialog .dialog-content h3 { + margin: 10px; +} + +.litegraph .dialog .dialog-content .connections { + flex-direction: row; +} + +.litegraph .dialog .dialog-content .connections .connections_side { + width: calc(50% - 5px); + min-height: 100px; + background-color: black; + display: flex; +} + +.litegraph .dialog .node_type { + font-size: 1.2em; + display: block; + margin: 10px; +} + +.litegraph .dialog .node_desc { + opacity: 0.5; + display: block; + margin: 10px; +} + +.litegraph .dialog .separator { + display: block; + width: calc( 100% - 4px ); + height: 1px; + border-top: 1px solid #000; + border-bottom: 1px solid #333; + margin: 10px 2px; + padding: 0; +} + +.litegraph .dialog .property { + margin-bottom: 2px; + padding: 4px; +} + +.litegraph .dialog .property_name { + color: #737373; + display: inline-block; + text-align: left; + vertical-align: top; + width: 120px; + padding-left: 4px; + overflow: hidden; +} + +.litegraph .dialog .property_value { + display: inline-block; + text-align: right; + color: #AAA; + background-color: #1A1A1A; + width: calc( 100% - 122px ); + max-height: 300px; + padding: 4px; + padding-right: 12px; + overflow: hidden; + cursor: pointer; + border-radius: 3px; +} + +.litegraph .dialog .property_value:hover { + color: white; +} + +.litegraph .dialog .property.boolean .property_value { + padding-right: 30px; +} + +.litegraph .dialog .btn { + border-radius: 4px; + padding: 4px 20px; + margin-left: 0px; + background-color: #060606; + color: #8e8e8e; +} + +.litegraph .dialog .btn:hover { + background-color: #111; + color: #FFF; +} + +.litegraph .dialog .btn.delete:hover { + background-color: #F33; + color: black; +} + +.litegraph .subgraph_property { + padding: 4px; +} + +.litegraph .subgraph_property:hover { + background-color: #333; +} + +.litegraph .subgraph_property.extra { + margin-top: 8px; +} + +.litegraph .subgraph_property span.name { + font-size: 1.3em; + padding-left: 4px; +} + +.litegraph .subgraph_property span.type { + opacity: 0.5; + margin-right: 20px; + padding-left: 4px; +} + +.litegraph .subgraph_property span.label { + display: inline-block; + width: 60px; + padding: 0px 10px; +} + +.litegraph .subgraph_property input { + width: 140px; + color: #999; + background-color: #1A1A1A; + border-radius: 4px; + border: 0; + margin-right: 10px; + padding: 4px; + padding-left: 10px; +} + +.litegraph .subgraph_property button { + background-color: #1c1c1c; + color: #aaa; + border: 0; + border-radius: 2px; + padding: 4px 10px; + cursor: pointer; +} + +.litegraph .subgraph_property.extra { + color: #ccc; +} + +.litegraph .subgraph_property.extra input { + background-color: #111; +} + +.litegraph .bullet_icon { + margin-left: 10px; + border-radius: 10px; + width: 12px; + height: 12px; + background-color: #666; + display: inline-block; + margin-top: 2px; + margin-right: 4px; + transition: background-color 0.1s ease 0s; + -moz-transition: background-color 0.1s ease 0s; +} + +.litegraph .bullet_icon:hover { + background-color: #698; + cursor: pointer; +} + /* OLD */ .graphcontextmenu { @@ -285,6 +513,7 @@ background-color: #333; font-size: 1.2em; box-shadow: 0 0 10px black !important; + z-index: 10; } .graphdialog.rounded { diff --git a/demo/demodata/audio.wav b/editor/demodata/audio.wav similarity index 100% rename from demo/demodata/audio.wav rename to editor/demodata/audio.wav diff --git a/demo/demodata/impulse.wav b/editor/demodata/impulse.wav similarity index 100% rename from demo/demodata/impulse.wav rename to editor/demodata/impulse.wav diff --git a/demo/demodata/video.webm b/editor/demodata/video.webm similarity index 100% rename from demo/demodata/video.webm rename to editor/demodata/video.webm diff --git a/demo/examples/audio.json b/editor/examples/audio.json similarity index 100% rename from demo/examples/audio.json rename to editor/examples/audio.json diff --git a/demo/examples/audio_delay.json b/editor/examples/audio_delay.json similarity index 100% rename from demo/examples/audio_delay.json rename to editor/examples/audio_delay.json diff --git a/demo/examples/audio_reverb.json b/editor/examples/audio_reverb.json similarity index 100% rename from demo/examples/audio_reverb.json rename to editor/examples/audio_reverb.json diff --git a/demo/examples/benchmark.json b/editor/examples/benchmark.json similarity index 100% rename from demo/examples/benchmark.json rename to editor/examples/benchmark.json diff --git a/demo/examples/features.json b/editor/examples/features.json similarity index 100% rename from demo/examples/features.json rename to editor/examples/features.json diff --git a/demo/examples/midi_generation.json b/editor/examples/midi_generation.json similarity index 100% rename from demo/examples/midi_generation.json rename to editor/examples/midi_generation.json diff --git a/demo/examples/subgraph.json b/editor/examples/subgraph.json similarity index 100% rename from demo/examples/subgraph.json rename to editor/examples/subgraph.json diff --git a/demo/imgs/grid.png b/editor/imgs/grid.png similarity index 100% rename from demo/imgs/grid.png rename to editor/imgs/grid.png diff --git a/demo/imgs/icon-edit.png b/editor/imgs/icon-edit.png similarity index 100% rename from demo/imgs/icon-edit.png rename to editor/imgs/icon-edit.png diff --git a/demo/imgs/icon-gear.png b/editor/imgs/icon-gear.png similarity index 100% rename from demo/imgs/icon-gear.png rename to editor/imgs/icon-gear.png diff --git a/demo/imgs/icon-load.png b/editor/imgs/icon-load.png similarity index 100% rename from demo/imgs/icon-load.png rename to editor/imgs/icon-load.png diff --git a/demo/imgs/icon-maximize.png b/editor/imgs/icon-maximize.png similarity index 100% rename from demo/imgs/icon-maximize.png rename to editor/imgs/icon-maximize.png diff --git a/demo/imgs/icon-play.png b/editor/imgs/icon-play.png similarity index 100% rename from demo/imgs/icon-play.png rename to editor/imgs/icon-play.png diff --git a/demo/imgs/icon-playstep.png b/editor/imgs/icon-playstep.png similarity index 100% rename from demo/imgs/icon-playstep.png rename to editor/imgs/icon-playstep.png diff --git a/demo/imgs/icon-record.png b/editor/imgs/icon-record.png similarity index 100% rename from demo/imgs/icon-record.png rename to editor/imgs/icon-record.png diff --git a/demo/imgs/icon-save.png b/editor/imgs/icon-save.png similarity index 100% rename from demo/imgs/icon-save.png rename to editor/imgs/icon-save.png diff --git a/demo/imgs/icon-stop.png b/editor/imgs/icon-stop.png similarity index 100% rename from demo/imgs/icon-stop.png rename to editor/imgs/icon-stop.png diff --git a/demo/imgs/load-progress-empty.png b/editor/imgs/load-progress-empty.png similarity index 100% rename from demo/imgs/load-progress-empty.png rename to editor/imgs/load-progress-empty.png diff --git a/demo/imgs/load-progress-full.png b/editor/imgs/load-progress-full.png similarity index 100% rename from demo/imgs/load-progress-full.png rename to editor/imgs/load-progress-full.png diff --git a/demo/imgs/load-progress-grey.png b/editor/imgs/load-progress-grey.png similarity index 100% rename from demo/imgs/load-progress-grey.png rename to editor/imgs/load-progress-grey.png diff --git a/demo/imgs/play-icons-light-alpha.png b/editor/imgs/play-icons-light-alpha.png similarity index 100% rename from demo/imgs/play-icons-light-alpha.png rename to editor/imgs/play-icons-light-alpha.png diff --git a/demo/imgs/play-icons-light.png b/editor/imgs/play-icons-light.png similarity index 100% rename from demo/imgs/play-icons-light.png rename to editor/imgs/play-icons-light.png diff --git a/demo/imgs/play-icons.png b/editor/imgs/play-icons.png similarity index 100% rename from demo/imgs/play-icons.png rename to editor/imgs/play-icons.png diff --git a/demo/index.html b/editor/index.html similarity index 83% rename from demo/index.html rename to editor/index.html index fbc99c364..01dce5054 100755 --- a/demo/index.html +++ b/editor/index.html @@ -15,7 +15,10 @@ + + + @@ -28,6 +31,7 @@ + diff --git a/demo/js/code.js b/editor/js/code.js similarity index 55% rename from demo/js/code.js rename to editor/js/code.js index c1b7972bb..177d77233 100644 --- a/demo/js/code.js +++ b/editor/js/code.js @@ -1,6 +1,7 @@ +var webgl_canvas = null; LiteGraph.node_images_path = "../nodes_data/"; -var editor = new LiteGraph.Editor("main"); +var editor = new LiteGraph.Editor("main",{miniwindow:false}); window.graphcanvas = editor.graphcanvas; window.graph = editor.graph; window.addEventListener("resize", function() { editor.graphcanvas.resize(); } ); @@ -10,10 +11,13 @@ window.onbeforeunload = function(){ localStorage.setItem("litegraphg demo backup", data ); } +//enable scripting +LiteGraph.allow_scripts = true; + //create scene selector var elem = document.createElement("span"); elem.className = "selector"; -elem.innerHTML = "Demo "; +elem.innerHTML = "Demo | "; editor.tools.appendChild(elem); var select = elem.querySelector("select"); select.addEventListener("change", function(e){ @@ -54,6 +58,9 @@ elem.querySelector("#download").addEventListener("click",function(){ setTimeout( function(){ URL.revokeObjectURL( url ); }, 1000*60 ); //wait one minute to revoke url }); +elem.querySelector("#webgl").addEventListener("click", enableWebGL ); + + function addDemo( name, url ) { var option = document.createElement("option"); @@ -81,5 +88,79 @@ addDemo("autobackup", function(){ graph.configure( graph_data ); }); +//allows to use the WebGL nodes like textures +function enableWebGL() +{ + if( webgl_canvas ) + { + webgl_canvas.style.display = (webgl_canvas.style.display == "none" ? "block" : "none"); + return; + } + var libs = [ + "js/libs/gl-matrix-min.js", + "js/libs/litegl.js", + "../src/nodes/gltextures.js", + "../src/nodes/glfx.js", + "../src/nodes/glshaders.js", + "../src/nodes/geometry.js" + ]; + function fetchJS() + { + if(libs.length == 0) + return on_ready(); + + var script = null; + script = document.createElement("script"); + script.onload = fetchJS; + script.src = libs.shift(); + document.head.appendChild(script); + } + + fetchJS(); + + function on_ready() + { + console.log(this.src); + if(!window.GL) + return; + webgl_canvas = document.createElement("canvas"); + webgl_canvas.width = 400; + webgl_canvas.height = 300; + webgl_canvas.style.position = "absolute"; + webgl_canvas.style.top = "0px"; + webgl_canvas.style.right = "0px"; + webgl_canvas.style.border = "1px solid #AAA"; + + webgl_canvas.addEventListener("click", function(){ + var rect = webgl_canvas.parentNode.getBoundingClientRect(); + if( webgl_canvas.width != rect.width ) + { + webgl_canvas.width = rect.width; + webgl_canvas.height = rect.height; + } + else + { + webgl_canvas.width = 400; + webgl_canvas.height = 300; + } + }); + + var parent = document.querySelector(".editor-area"); + parent.appendChild( webgl_canvas ); + var gl = GL.create({ canvas: webgl_canvas }); + if(!gl) + return; + + editor.graph.onBeforeStep = ondraw; + + console.log("webgl ready"); + function ondraw () + { + gl.clearColor(0,0,0,0); + gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT ); + gl.viewport(0,0,gl.canvas.width, gl.canvas.height ); + } + } +} diff --git a/demo/js/demos.js b/editor/js/demos.js similarity index 97% rename from demo/js/demos.js rename to editor/js/demos.js index 921704f66..f09631c14 100644 --- a/demo/js/demos.js +++ b/editor/js/demos.js @@ -91,6 +91,8 @@ function TestWidgetsNode() this.text = this.addWidget("text","Text", "edit me", function(v){}, {} ); this.toggle = this.addWidget("toggle","Toggle", true, function(v){}, { on: "enabled", off:"disabled"} ); this.button = this.addWidget("button","Button", null, function(v){}, {} ); + this.toggle2 = this.addWidget("toggle","Disabled", true, function(v){}, { on: "enabled", off:"disabled"} ); + this.toggle2.disabled = true; this.size = this.computeSize(); this.serialize_widgets = true; } @@ -99,7 +101,6 @@ TestWidgetsNode.title = "Widgets"; LiteGraph.registerNodeType("features/widgets", TestWidgetsNode ); - //Show value inside the debug console function TestSpecialNode() { diff --git a/demo/js/libs/audiosynth.js b/editor/js/libs/audiosynth.js similarity index 100% rename from demo/js/libs/audiosynth.js rename to editor/js/libs/audiosynth.js diff --git a/editor/js/libs/gl-matrix-min.js b/editor/js/libs/gl-matrix-min.js new file mode 100644 index 000000000..747b3a77e --- /dev/null +++ b/editor/js/libs/gl-matrix-min.js @@ -0,0 +1,28 @@ +/*! +@fileoverview gl-matrix - High performance matrix and vector operations +@author Brandon Jones +@author Colin MacKenzie IV +@version 2.7.0 + +Copyright (c) 2015-2018, Brandon Jones, Colin MacKenzie IV. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ +!function(t,n){if("object"==typeof exports&&"object"==typeof module)module.exports=n();else if("function"==typeof define&&define.amd)define([],n);else{var r=n();for(var a in r)("object"==typeof exports?exports:t)[a]=r[a]}}("undefined"!=typeof self?self:this,function(){return function(t){var n={};function r(a){if(n[a])return n[a].exports;var e=n[a]={i:a,l:!1,exports:{}};return t[a].call(e.exports,e,e.exports,r),e.l=!0,e.exports}return r.m=t,r.c=n,r.d=function(t,n,a){r.o(t,n)||Object.defineProperty(t,n,{enumerable:!0,get:a})},r.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},r.t=function(t,n){if(1&n&&(t=r(t)),8&n)return t;if(4&n&&"object"==typeof t&&t&&t.__esModule)return t;var a=Object.create(null);if(r.r(a),Object.defineProperty(a,"default",{enumerable:!0,value:t}),2&n&&"string"!=typeof t)for(var e in t)r.d(a,e,function(n){return t[n]}.bind(null,e));return a},r.n=function(t){var n=t&&t.__esModule?function(){return t.default}:function(){return t};return r.d(n,"a",n),n},r.o=function(t,n){return Object.prototype.hasOwnProperty.call(t,n)},r.p="",r(r.s=10)}([function(t,n,r){"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.setMatrixArrayType=function(t){n.ARRAY_TYPE=t},n.toRadian=function(t){return t*e},n.equals=function(t,n){return Math.abs(t-n)<=a*Math.max(1,Math.abs(t),Math.abs(n))};var a=n.EPSILON=1e-6;n.ARRAY_TYPE="undefined"!=typeof Float32Array?Float32Array:Array,n.RANDOM=Math.random;var e=Math.PI/180},function(t,n,r){"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.forEach=n.sqrLen=n.len=n.sqrDist=n.dist=n.div=n.mul=n.sub=void 0,n.create=e,n.clone=function(t){var n=new a.ARRAY_TYPE(4);return n[0]=t[0],n[1]=t[1],n[2]=t[2],n[3]=t[3],n},n.fromValues=function(t,n,r,e){var u=new a.ARRAY_TYPE(4);return u[0]=t,u[1]=n,u[2]=r,u[3]=e,u},n.copy=function(t,n){return t[0]=n[0],t[1]=n[1],t[2]=n[2],t[3]=n[3],t},n.set=function(t,n,r,a,e){return t[0]=n,t[1]=r,t[2]=a,t[3]=e,t},n.add=function(t,n,r){return t[0]=n[0]+r[0],t[1]=n[1]+r[1],t[2]=n[2]+r[2],t[3]=n[3]+r[3],t},n.subtract=u,n.multiply=o,n.divide=i,n.ceil=function(t,n){return t[0]=Math.ceil(n[0]),t[1]=Math.ceil(n[1]),t[2]=Math.ceil(n[2]),t[3]=Math.ceil(n[3]),t},n.floor=function(t,n){return t[0]=Math.floor(n[0]),t[1]=Math.floor(n[1]),t[2]=Math.floor(n[2]),t[3]=Math.floor(n[3]),t},n.min=function(t,n,r){return t[0]=Math.min(n[0],r[0]),t[1]=Math.min(n[1],r[1]),t[2]=Math.min(n[2],r[2]),t[3]=Math.min(n[3],r[3]),t},n.max=function(t,n,r){return t[0]=Math.max(n[0],r[0]),t[1]=Math.max(n[1],r[1]),t[2]=Math.max(n[2],r[2]),t[3]=Math.max(n[3],r[3]),t},n.round=function(t,n){return t[0]=Math.round(n[0]),t[1]=Math.round(n[1]),t[2]=Math.round(n[2]),t[3]=Math.round(n[3]),t},n.scale=function(t,n,r){return t[0]=n[0]*r,t[1]=n[1]*r,t[2]=n[2]*r,t[3]=n[3]*r,t},n.scaleAndAdd=function(t,n,r,a){return t[0]=n[0]+r[0]*a,t[1]=n[1]+r[1]*a,t[2]=n[2]+r[2]*a,t[3]=n[3]+r[3]*a,t},n.distance=s,n.squaredDistance=c,n.length=f,n.squaredLength=M,n.negate=function(t,n){return t[0]=-n[0],t[1]=-n[1],t[2]=-n[2],t[3]=-n[3],t},n.inverse=function(t,n){return t[0]=1/n[0],t[1]=1/n[1],t[2]=1/n[2],t[3]=1/n[3],t},n.normalize=function(t,n){var r=n[0],a=n[1],e=n[2],u=n[3],o=r*r+a*a+e*e+u*u;o>0&&(o=1/Math.sqrt(o),t[0]=r*o,t[1]=a*o,t[2]=e*o,t[3]=u*o);return t},n.dot=function(t,n){return t[0]*n[0]+t[1]*n[1]+t[2]*n[2]+t[3]*n[3]},n.lerp=function(t,n,r,a){var e=n[0],u=n[1],o=n[2],i=n[3];return t[0]=e+a*(r[0]-e),t[1]=u+a*(r[1]-u),t[2]=o+a*(r[2]-o),t[3]=i+a*(r[3]-i),t},n.random=function(t,n){var r,e,u,o,i,s;n=n||1;do{r=2*a.RANDOM()-1,e=2*a.RANDOM()-1,i=r*r+e*e}while(i>=1);do{u=2*a.RANDOM()-1,o=2*a.RANDOM()-1,s=u*u+o*o}while(s>=1);var c=Math.sqrt((1-i)/s);return t[0]=n*r,t[1]=n*e,t[2]=n*u*c,t[3]=n*o*c,t},n.transformMat4=function(t,n,r){var a=n[0],e=n[1],u=n[2],o=n[3];return t[0]=r[0]*a+r[4]*e+r[8]*u+r[12]*o,t[1]=r[1]*a+r[5]*e+r[9]*u+r[13]*o,t[2]=r[2]*a+r[6]*e+r[10]*u+r[14]*o,t[3]=r[3]*a+r[7]*e+r[11]*u+r[15]*o,t},n.transformQuat=function(t,n,r){var a=n[0],e=n[1],u=n[2],o=r[0],i=r[1],s=r[2],c=r[3],f=c*a+i*u-s*e,M=c*e+s*a-o*u,h=c*u+o*e-i*a,l=-o*a-i*e-s*u;return t[0]=f*c+l*-o+M*-s-h*-i,t[1]=M*c+l*-i+h*-o-f*-s,t[2]=h*c+l*-s+f*-i-M*-o,t[3]=n[3],t},n.str=function(t){return"vec4("+t[0]+", "+t[1]+", "+t[2]+", "+t[3]+")"},n.exactEquals=function(t,n){return t[0]===n[0]&&t[1]===n[1]&&t[2]===n[2]&&t[3]===n[3]},n.equals=function(t,n){var r=t[0],e=t[1],u=t[2],o=t[3],i=n[0],s=n[1],c=n[2],f=n[3];return Math.abs(r-i)<=a.EPSILON*Math.max(1,Math.abs(r),Math.abs(i))&&Math.abs(e-s)<=a.EPSILON*Math.max(1,Math.abs(e),Math.abs(s))&&Math.abs(u-c)<=a.EPSILON*Math.max(1,Math.abs(u),Math.abs(c))&&Math.abs(o-f)<=a.EPSILON*Math.max(1,Math.abs(o),Math.abs(f))};var a=function(t){if(t&&t.__esModule)return t;var n={};if(null!=t)for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(n[r]=t[r]);return n.default=t,n}(r(0));function e(){var t=new a.ARRAY_TYPE(4);return a.ARRAY_TYPE!=Float32Array&&(t[0]=0,t[1]=0,t[2]=0,t[3]=0),t}function u(t,n,r){return t[0]=n[0]-r[0],t[1]=n[1]-r[1],t[2]=n[2]-r[2],t[3]=n[3]-r[3],t}function o(t,n,r){return t[0]=n[0]*r[0],t[1]=n[1]*r[1],t[2]=n[2]*r[2],t[3]=n[3]*r[3],t}function i(t,n,r){return t[0]=n[0]/r[0],t[1]=n[1]/r[1],t[2]=n[2]/r[2],t[3]=n[3]/r[3],t}function s(t,n){var r=n[0]-t[0],a=n[1]-t[1],e=n[2]-t[2],u=n[3]-t[3];return Math.sqrt(r*r+a*a+e*e+u*u)}function c(t,n){var r=n[0]-t[0],a=n[1]-t[1],e=n[2]-t[2],u=n[3]-t[3];return r*r+a*a+e*e+u*u}function f(t){var n=t[0],r=t[1],a=t[2],e=t[3];return Math.sqrt(n*n+r*r+a*a+e*e)}function M(t){var n=t[0],r=t[1],a=t[2],e=t[3];return n*n+r*r+a*a+e*e}n.sub=u,n.mul=o,n.div=i,n.dist=s,n.sqrDist=c,n.len=f,n.sqrLen=M,n.forEach=function(){var t=e();return function(n,r,a,e,u,o){var i=void 0,s=void 0;for(r||(r=4),a||(a=0),s=e?Math.min(e*r+a,n.length):n.length,i=a;i1?0:e<-1?Math.PI:Math.acos(e)},n.str=function(t){return"vec3("+t[0]+", "+t[1]+", "+t[2]+")"},n.exactEquals=function(t,n){return t[0]===n[0]&&t[1]===n[1]&&t[2]===n[2]},n.equals=function(t,n){var r=t[0],e=t[1],u=t[2],o=n[0],i=n[1],s=n[2];return Math.abs(r-o)<=a.EPSILON*Math.max(1,Math.abs(r),Math.abs(o))&&Math.abs(e-i)<=a.EPSILON*Math.max(1,Math.abs(e),Math.abs(i))&&Math.abs(u-s)<=a.EPSILON*Math.max(1,Math.abs(u),Math.abs(s))};var a=function(t){if(t&&t.__esModule)return t;var n={};if(null!=t)for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(n[r]=t[r]);return n.default=t,n}(r(0));function e(){var t=new a.ARRAY_TYPE(3);return a.ARRAY_TYPE!=Float32Array&&(t[0]=0,t[1]=0,t[2]=0),t}function u(t){var n=t[0],r=t[1],a=t[2];return Math.sqrt(n*n+r*r+a*a)}function o(t,n,r){var e=new a.ARRAY_TYPE(3);return e[0]=t,e[1]=n,e[2]=r,e}function i(t,n,r){return t[0]=n[0]-r[0],t[1]=n[1]-r[1],t[2]=n[2]-r[2],t}function s(t,n,r){return t[0]=n[0]*r[0],t[1]=n[1]*r[1],t[2]=n[2]*r[2],t}function c(t,n,r){return t[0]=n[0]/r[0],t[1]=n[1]/r[1],t[2]=n[2]/r[2],t}function f(t,n){var r=n[0]-t[0],a=n[1]-t[1],e=n[2]-t[2];return Math.sqrt(r*r+a*a+e*e)}function M(t,n){var r=n[0]-t[0],a=n[1]-t[1],e=n[2]-t[2];return r*r+a*a+e*e}function h(t){var n=t[0],r=t[1],a=t[2];return n*n+r*r+a*a}function l(t,n){var r=n[0],a=n[1],e=n[2],u=r*r+a*a+e*e;return u>0&&(u=1/Math.sqrt(u),t[0]=n[0]*u,t[1]=n[1]*u,t[2]=n[2]*u),t}function v(t,n){return t[0]*n[0]+t[1]*n[1]+t[2]*n[2]}n.sub=i,n.mul=s,n.div=c,n.dist=f,n.sqrDist=M,n.len=u,n.sqrLen=h,n.forEach=function(){var t=e();return function(n,r,a,e,u,o){var i=void 0,s=void 0;for(r||(r=3),a||(a=0),s=e?Math.min(e*r+a,n.length):n.length,i=a;ia.EPSILON?(t[0]=n[0]/e,t[1]=n[1]/e,t[2]=n[2]/e):(t[0]=1,t[1]=0,t[2]=0);return r},n.multiply=f,n.rotateX=function(t,n,r){r*=.5;var a=n[0],e=n[1],u=n[2],o=n[3],i=Math.sin(r),s=Math.cos(r);return t[0]=a*s+o*i,t[1]=e*s+u*i,t[2]=u*s-e*i,t[3]=o*s-a*i,t},n.rotateY=function(t,n,r){r*=.5;var a=n[0],e=n[1],u=n[2],o=n[3],i=Math.sin(r),s=Math.cos(r);return t[0]=a*s-u*i,t[1]=e*s+o*i,t[2]=u*s+a*i,t[3]=o*s-e*i,t},n.rotateZ=function(t,n,r){r*=.5;var a=n[0],e=n[1],u=n[2],o=n[3],i=Math.sin(r),s=Math.cos(r);return t[0]=a*s+e*i,t[1]=e*s-a*i,t[2]=u*s+o*i,t[3]=o*s-u*i,t},n.calculateW=function(t,n){var r=n[0],a=n[1],e=n[2];return t[0]=r,t[1]=a,t[2]=e,t[3]=Math.sqrt(Math.abs(1-r*r-a*a-e*e)),t},n.slerp=M,n.random=function(t){var n=a.RANDOM(),r=a.RANDOM(),e=a.RANDOM(),u=Math.sqrt(1-n),o=Math.sqrt(n);return t[0]=u*Math.sin(2*Math.PI*r),t[1]=u*Math.cos(2*Math.PI*r),t[2]=o*Math.sin(2*Math.PI*e),t[3]=o*Math.cos(2*Math.PI*e),t},n.invert=function(t,n){var r=n[0],a=n[1],e=n[2],u=n[3],o=r*r+a*a+e*e+u*u,i=o?1/o:0;return t[0]=-r*i,t[1]=-a*i,t[2]=-e*i,t[3]=u*i,t},n.conjugate=function(t,n){return t[0]=-n[0],t[1]=-n[1],t[2]=-n[2],t[3]=n[3],t},n.fromMat3=h,n.fromEuler=function(t,n,r,a){var e=.5*Math.PI/180;n*=e,r*=e,a*=e;var u=Math.sin(n),o=Math.cos(n),i=Math.sin(r),s=Math.cos(r),c=Math.sin(a),f=Math.cos(a);return t[0]=u*s*f-o*i*c,t[1]=o*i*f+u*s*c,t[2]=o*s*c-u*i*f,t[3]=o*s*f+u*i*c,t},n.str=function(t){return"quat("+t[0]+", "+t[1]+", "+t[2]+", "+t[3]+")"};var a=i(r(0)),e=i(r(5)),u=i(r(2)),o=i(r(1));function i(t){if(t&&t.__esModule)return t;var n={};if(null!=t)for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(n[r]=t[r]);return n.default=t,n}function s(){var t=new a.ARRAY_TYPE(4);return a.ARRAY_TYPE!=Float32Array&&(t[0]=0,t[1]=0,t[2]=0),t[3]=1,t}function c(t,n,r){r*=.5;var a=Math.sin(r);return t[0]=a*n[0],t[1]=a*n[1],t[2]=a*n[2],t[3]=Math.cos(r),t}function f(t,n,r){var a=n[0],e=n[1],u=n[2],o=n[3],i=r[0],s=r[1],c=r[2],f=r[3];return t[0]=a*f+o*i+e*c-u*s,t[1]=e*f+o*s+u*i-a*c,t[2]=u*f+o*c+a*s-e*i,t[3]=o*f-a*i-e*s-u*c,t}function M(t,n,r,e){var u=n[0],o=n[1],i=n[2],s=n[3],c=r[0],f=r[1],M=r[2],h=r[3],l=void 0,v=void 0,d=void 0,b=void 0,m=void 0;return(v=u*c+o*f+i*M+s*h)<0&&(v=-v,c=-c,f=-f,M=-M,h=-h),1-v>a.EPSILON?(l=Math.acos(v),d=Math.sin(l),b=Math.sin((1-e)*l)/d,m=Math.sin(e*l)/d):(b=1-e,m=e),t[0]=b*u+m*c,t[1]=b*o+m*f,t[2]=b*i+m*M,t[3]=b*s+m*h,t}function h(t,n){var r=n[0]+n[4]+n[8],a=void 0;if(r>0)a=Math.sqrt(r+1),t[3]=.5*a,a=.5/a,t[0]=(n[5]-n[7])*a,t[1]=(n[6]-n[2])*a,t[2]=(n[1]-n[3])*a;else{var e=0;n[4]>n[0]&&(e=1),n[8]>n[3*e+e]&&(e=2);var u=(e+1)%3,o=(e+2)%3;a=Math.sqrt(n[3*e+e]-n[3*u+u]-n[3*o+o]+1),t[e]=.5*a,a=.5/a,t[3]=(n[3*u+o]-n[3*o+u])*a,t[u]=(n[3*u+e]+n[3*e+u])*a,t[o]=(n[3*o+e]+n[3*e+o])*a}return t}n.clone=o.clone,n.fromValues=o.fromValues,n.copy=o.copy,n.set=o.set,n.add=o.add,n.mul=f,n.scale=o.scale,n.dot=o.dot,n.lerp=o.lerp;var l=n.length=o.length,v=(n.len=l,n.squaredLength=o.squaredLength),d=(n.sqrLen=v,n.normalize=o.normalize);n.exactEquals=o.exactEquals,n.equals=o.equals,n.rotationTo=function(){var t=u.create(),n=u.fromValues(1,0,0),r=u.fromValues(0,1,0);return function(a,e,o){var i=u.dot(e,o);return i<-.999999?(u.cross(t,n,e),u.len(t)<1e-6&&u.cross(t,r,e),u.normalize(t,t),c(a,t,Math.PI),a):i>.999999?(a[0]=0,a[1]=0,a[2]=0,a[3]=1,a):(u.cross(t,e,o),a[0]=t[0],a[1]=t[1],a[2]=t[2],a[3]=1+i,d(a,a))}}(),n.sqlerp=function(){var t=s(),n=s();return function(r,a,e,u,o,i){return M(t,a,o,i),M(n,e,u,i),M(r,t,n,2*i*(1-i)),r}}(),n.setAxes=function(){var t=e.create();return function(n,r,a,e){return t[0]=a[0],t[3]=a[1],t[6]=a[2],t[1]=e[0],t[4]=e[1],t[7]=e[2],t[2]=-r[0],t[5]=-r[1],t[8]=-r[2],d(n,h(n,t))}}()},function(t,n,r){"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.sub=n.mul=void 0,n.create=function(){var t=new a.ARRAY_TYPE(16);a.ARRAY_TYPE!=Float32Array&&(t[1]=0,t[2]=0,t[3]=0,t[4]=0,t[6]=0,t[7]=0,t[8]=0,t[9]=0,t[11]=0,t[12]=0,t[13]=0,t[14]=0);return t[0]=1,t[5]=1,t[10]=1,t[15]=1,t},n.clone=function(t){var n=new a.ARRAY_TYPE(16);return n[0]=t[0],n[1]=t[1],n[2]=t[2],n[3]=t[3],n[4]=t[4],n[5]=t[5],n[6]=t[6],n[7]=t[7],n[8]=t[8],n[9]=t[9],n[10]=t[10],n[11]=t[11],n[12]=t[12],n[13]=t[13],n[14]=t[14],n[15]=t[15],n},n.copy=function(t,n){return t[0]=n[0],t[1]=n[1],t[2]=n[2],t[3]=n[3],t[4]=n[4],t[5]=n[5],t[6]=n[6],t[7]=n[7],t[8]=n[8],t[9]=n[9],t[10]=n[10],t[11]=n[11],t[12]=n[12],t[13]=n[13],t[14]=n[14],t[15]=n[15],t},n.fromValues=function(t,n,r,e,u,o,i,s,c,f,M,h,l,v,d,b){var m=new a.ARRAY_TYPE(16);return m[0]=t,m[1]=n,m[2]=r,m[3]=e,m[4]=u,m[5]=o,m[6]=i,m[7]=s,m[8]=c,m[9]=f,m[10]=M,m[11]=h,m[12]=l,m[13]=v,m[14]=d,m[15]=b,m},n.set=function(t,n,r,a,e,u,o,i,s,c,f,M,h,l,v,d,b){return t[0]=n,t[1]=r,t[2]=a,t[3]=e,t[4]=u,t[5]=o,t[6]=i,t[7]=s,t[8]=c,t[9]=f,t[10]=M,t[11]=h,t[12]=l,t[13]=v,t[14]=d,t[15]=b,t},n.identity=e,n.transpose=function(t,n){if(t===n){var r=n[1],a=n[2],e=n[3],u=n[6],o=n[7],i=n[11];t[1]=n[4],t[2]=n[8],t[3]=n[12],t[4]=r,t[6]=n[9],t[7]=n[13],t[8]=a,t[9]=u,t[11]=n[14],t[12]=e,t[13]=o,t[14]=i}else t[0]=n[0],t[1]=n[4],t[2]=n[8],t[3]=n[12],t[4]=n[1],t[5]=n[5],t[6]=n[9],t[7]=n[13],t[8]=n[2],t[9]=n[6],t[10]=n[10],t[11]=n[14],t[12]=n[3],t[13]=n[7],t[14]=n[11],t[15]=n[15];return t},n.invert=function(t,n){var r=n[0],a=n[1],e=n[2],u=n[3],o=n[4],i=n[5],s=n[6],c=n[7],f=n[8],M=n[9],h=n[10],l=n[11],v=n[12],d=n[13],b=n[14],m=n[15],p=r*i-a*o,P=r*s-e*o,A=r*c-u*o,E=a*s-e*i,O=a*c-u*i,R=e*c-u*s,y=f*d-M*v,q=f*b-h*v,x=f*m-l*v,_=M*b-h*d,Y=M*m-l*d,L=h*m-l*b,S=p*L-P*Y+A*_+E*x-O*q+R*y;if(!S)return null;return S=1/S,t[0]=(i*L-s*Y+c*_)*S,t[1]=(e*Y-a*L-u*_)*S,t[2]=(d*R-b*O+m*E)*S,t[3]=(h*O-M*R-l*E)*S,t[4]=(s*x-o*L-c*q)*S,t[5]=(r*L-e*x+u*q)*S,t[6]=(b*A-v*R-m*P)*S,t[7]=(f*R-h*A+l*P)*S,t[8]=(o*Y-i*x+c*y)*S,t[9]=(a*x-r*Y-u*y)*S,t[10]=(v*O-d*A+m*p)*S,t[11]=(M*A-f*O-l*p)*S,t[12]=(i*q-o*_-s*y)*S,t[13]=(r*_-a*q+e*y)*S,t[14]=(d*P-v*E-b*p)*S,t[15]=(f*E-M*P+h*p)*S,t},n.adjoint=function(t,n){var r=n[0],a=n[1],e=n[2],u=n[3],o=n[4],i=n[5],s=n[6],c=n[7],f=n[8],M=n[9],h=n[10],l=n[11],v=n[12],d=n[13],b=n[14],m=n[15];return t[0]=i*(h*m-l*b)-M*(s*m-c*b)+d*(s*l-c*h),t[1]=-(a*(h*m-l*b)-M*(e*m-u*b)+d*(e*l-u*h)),t[2]=a*(s*m-c*b)-i*(e*m-u*b)+d*(e*c-u*s),t[3]=-(a*(s*l-c*h)-i*(e*l-u*h)+M*(e*c-u*s)),t[4]=-(o*(h*m-l*b)-f*(s*m-c*b)+v*(s*l-c*h)),t[5]=r*(h*m-l*b)-f*(e*m-u*b)+v*(e*l-u*h),t[6]=-(r*(s*m-c*b)-o*(e*m-u*b)+v*(e*c-u*s)),t[7]=r*(s*l-c*h)-o*(e*l-u*h)+f*(e*c-u*s),t[8]=o*(M*m-l*d)-f*(i*m-c*d)+v*(i*l-c*M),t[9]=-(r*(M*m-l*d)-f*(a*m-u*d)+v*(a*l-u*M)),t[10]=r*(i*m-c*d)-o*(a*m-u*d)+v*(a*c-u*i),t[11]=-(r*(i*l-c*M)-o*(a*l-u*M)+f*(a*c-u*i)),t[12]=-(o*(M*b-h*d)-f*(i*b-s*d)+v*(i*h-s*M)),t[13]=r*(M*b-h*d)-f*(a*b-e*d)+v*(a*h-e*M),t[14]=-(r*(i*b-s*d)-o*(a*b-e*d)+v*(a*s-e*i)),t[15]=r*(i*h-s*M)-o*(a*h-e*M)+f*(a*s-e*i),t},n.determinant=function(t){var n=t[0],r=t[1],a=t[2],e=t[3],u=t[4],o=t[5],i=t[6],s=t[7],c=t[8],f=t[9],M=t[10],h=t[11],l=t[12],v=t[13],d=t[14],b=t[15];return(n*o-r*u)*(M*b-h*d)-(n*i-a*u)*(f*b-h*v)+(n*s-e*u)*(f*d-M*v)+(r*i-a*o)*(c*b-h*l)-(r*s-e*o)*(c*d-M*l)+(a*s-e*i)*(c*v-f*l)},n.multiply=u,n.translate=function(t,n,r){var a=r[0],e=r[1],u=r[2],o=void 0,i=void 0,s=void 0,c=void 0,f=void 0,M=void 0,h=void 0,l=void 0,v=void 0,d=void 0,b=void 0,m=void 0;n===t?(t[12]=n[0]*a+n[4]*e+n[8]*u+n[12],t[13]=n[1]*a+n[5]*e+n[9]*u+n[13],t[14]=n[2]*a+n[6]*e+n[10]*u+n[14],t[15]=n[3]*a+n[7]*e+n[11]*u+n[15]):(o=n[0],i=n[1],s=n[2],c=n[3],f=n[4],M=n[5],h=n[6],l=n[7],v=n[8],d=n[9],b=n[10],m=n[11],t[0]=o,t[1]=i,t[2]=s,t[3]=c,t[4]=f,t[5]=M,t[6]=h,t[7]=l,t[8]=v,t[9]=d,t[10]=b,t[11]=m,t[12]=o*a+f*e+v*u+n[12],t[13]=i*a+M*e+d*u+n[13],t[14]=s*a+h*e+b*u+n[14],t[15]=c*a+l*e+m*u+n[15]);return t},n.scale=function(t,n,r){var a=r[0],e=r[1],u=r[2];return t[0]=n[0]*a,t[1]=n[1]*a,t[2]=n[2]*a,t[3]=n[3]*a,t[4]=n[4]*e,t[5]=n[5]*e,t[6]=n[6]*e,t[7]=n[7]*e,t[8]=n[8]*u,t[9]=n[9]*u,t[10]=n[10]*u,t[11]=n[11]*u,t[12]=n[12],t[13]=n[13],t[14]=n[14],t[15]=n[15],t},n.rotate=function(t,n,r,e){var u=e[0],o=e[1],i=e[2],s=Math.sqrt(u*u+o*o+i*i),c=void 0,f=void 0,M=void 0,h=void 0,l=void 0,v=void 0,d=void 0,b=void 0,m=void 0,p=void 0,P=void 0,A=void 0,E=void 0,O=void 0,R=void 0,y=void 0,q=void 0,x=void 0,_=void 0,Y=void 0,L=void 0,S=void 0,w=void 0,I=void 0;if(s0?(r[0]=2*(c*s+h*e+f*i-M*u)/l,r[1]=2*(f*s+h*u+M*e-c*i)/l,r[2]=2*(M*s+h*i+c*u-f*e)/l):(r[0]=2*(c*s+h*e+f*i-M*u),r[1]=2*(f*s+h*u+M*e-c*i),r[2]=2*(M*s+h*i+c*u-f*e));return o(t,n,r),t},n.getTranslation=function(t,n){return t[0]=n[12],t[1]=n[13],t[2]=n[14],t},n.getScaling=function(t,n){var r=n[0],a=n[1],e=n[2],u=n[4],o=n[5],i=n[6],s=n[8],c=n[9],f=n[10];return t[0]=Math.sqrt(r*r+a*a+e*e),t[1]=Math.sqrt(u*u+o*o+i*i),t[2]=Math.sqrt(s*s+c*c+f*f),t},n.getRotation=function(t,n){var r=n[0]+n[5]+n[10],a=0;r>0?(a=2*Math.sqrt(r+1),t[3]=.25*a,t[0]=(n[6]-n[9])/a,t[1]=(n[8]-n[2])/a,t[2]=(n[1]-n[4])/a):n[0]>n[5]&&n[0]>n[10]?(a=2*Math.sqrt(1+n[0]-n[5]-n[10]),t[3]=(n[6]-n[9])/a,t[0]=.25*a,t[1]=(n[1]+n[4])/a,t[2]=(n[8]+n[2])/a):n[5]>n[10]?(a=2*Math.sqrt(1+n[5]-n[0]-n[10]),t[3]=(n[8]-n[2])/a,t[0]=(n[1]+n[4])/a,t[1]=.25*a,t[2]=(n[6]+n[9])/a):(a=2*Math.sqrt(1+n[10]-n[0]-n[5]),t[3]=(n[1]-n[4])/a,t[0]=(n[8]+n[2])/a,t[1]=(n[6]+n[9])/a,t[2]=.25*a);return t},n.fromRotationTranslationScale=function(t,n,r,a){var e=n[0],u=n[1],o=n[2],i=n[3],s=e+e,c=u+u,f=o+o,M=e*s,h=e*c,l=e*f,v=u*c,d=u*f,b=o*f,m=i*s,p=i*c,P=i*f,A=a[0],E=a[1],O=a[2];return t[0]=(1-(v+b))*A,t[1]=(h+P)*A,t[2]=(l-p)*A,t[3]=0,t[4]=(h-P)*E,t[5]=(1-(M+b))*E,t[6]=(d+m)*E,t[7]=0,t[8]=(l+p)*O,t[9]=(d-m)*O,t[10]=(1-(M+v))*O,t[11]=0,t[12]=r[0],t[13]=r[1],t[14]=r[2],t[15]=1,t},n.fromRotationTranslationScaleOrigin=function(t,n,r,a,e){var u=n[0],o=n[1],i=n[2],s=n[3],c=u+u,f=o+o,M=i+i,h=u*c,l=u*f,v=u*M,d=o*f,b=o*M,m=i*M,p=s*c,P=s*f,A=s*M,E=a[0],O=a[1],R=a[2],y=e[0],q=e[1],x=e[2],_=(1-(d+m))*E,Y=(l+A)*E,L=(v-P)*E,S=(l-A)*O,w=(1-(h+m))*O,I=(b+p)*O,N=(v+P)*R,g=(b-p)*R,T=(1-(h+d))*R;return t[0]=_,t[1]=Y,t[2]=L,t[3]=0,t[4]=S,t[5]=w,t[6]=I,t[7]=0,t[8]=N,t[9]=g,t[10]=T,t[11]=0,t[12]=r[0]+y-(_*y+S*q+N*x),t[13]=r[1]+q-(Y*y+w*q+g*x),t[14]=r[2]+x-(L*y+I*q+T*x),t[15]=1,t},n.fromQuat=function(t,n){var r=n[0],a=n[1],e=n[2],u=n[3],o=r+r,i=a+a,s=e+e,c=r*o,f=a*o,M=a*i,h=e*o,l=e*i,v=e*s,d=u*o,b=u*i,m=u*s;return t[0]=1-M-v,t[1]=f+m,t[2]=h-b,t[3]=0,t[4]=f-m,t[5]=1-c-v,t[6]=l+d,t[7]=0,t[8]=h+b,t[9]=l-d,t[10]=1-c-M,t[11]=0,t[12]=0,t[13]=0,t[14]=0,t[15]=1,t},n.frustum=function(t,n,r,a,e,u,o){var i=1/(r-n),s=1/(e-a),c=1/(u-o);return t[0]=2*u*i,t[1]=0,t[2]=0,t[3]=0,t[4]=0,t[5]=2*u*s,t[6]=0,t[7]=0,t[8]=(r+n)*i,t[9]=(e+a)*s,t[10]=(o+u)*c,t[11]=-1,t[12]=0,t[13]=0,t[14]=o*u*2*c,t[15]=0,t},n.perspective=function(t,n,r,a,e){var u=1/Math.tan(n/2),o=void 0;t[0]=u/r,t[1]=0,t[2]=0,t[3]=0,t[4]=0,t[5]=u,t[6]=0,t[7]=0,t[8]=0,t[9]=0,t[11]=-1,t[12]=0,t[13]=0,t[15]=0,null!=e&&e!==1/0?(o=1/(a-e),t[10]=(e+a)*o,t[14]=2*e*a*o):(t[10]=-1,t[14]=-2*a);return t},n.perspectiveFromFieldOfView=function(t,n,r,a){var e=Math.tan(n.upDegrees*Math.PI/180),u=Math.tan(n.downDegrees*Math.PI/180),o=Math.tan(n.leftDegrees*Math.PI/180),i=Math.tan(n.rightDegrees*Math.PI/180),s=2/(o+i),c=2/(e+u);return t[0]=s,t[1]=0,t[2]=0,t[3]=0,t[4]=0,t[5]=c,t[6]=0,t[7]=0,t[8]=-(o-i)*s*.5,t[9]=(e-u)*c*.5,t[10]=a/(r-a),t[11]=-1,t[12]=0,t[13]=0,t[14]=a*r/(r-a),t[15]=0,t},n.ortho=function(t,n,r,a,e,u,o){var i=1/(n-r),s=1/(a-e),c=1/(u-o);return t[0]=-2*i,t[1]=0,t[2]=0,t[3]=0,t[4]=0,t[5]=-2*s,t[6]=0,t[7]=0,t[8]=0,t[9]=0,t[10]=2*c,t[11]=0,t[12]=(n+r)*i,t[13]=(e+a)*s,t[14]=(o+u)*c,t[15]=1,t},n.lookAt=function(t,n,r,u){var o=void 0,i=void 0,s=void 0,c=void 0,f=void 0,M=void 0,h=void 0,l=void 0,v=void 0,d=void 0,b=n[0],m=n[1],p=n[2],P=u[0],A=u[1],E=u[2],O=r[0],R=r[1],y=r[2];if(Math.abs(b-O)0&&(l=1/Math.sqrt(l),f*=l,M*=l,h*=l);var v=s*h-c*M,d=c*f-i*h,b=i*M-s*f;(l=v*v+d*d+b*b)>0&&(l=1/Math.sqrt(l),v*=l,d*=l,b*=l);return t[0]=v,t[1]=d,t[2]=b,t[3]=0,t[4]=M*b-h*d,t[5]=h*v-f*b,t[6]=f*d-M*v,t[7]=0,t[8]=f,t[9]=M,t[10]=h,t[11]=0,t[12]=e,t[13]=u,t[14]=o,t[15]=1,t},n.str=function(t){return"mat4("+t[0]+", "+t[1]+", "+t[2]+", "+t[3]+", "+t[4]+", "+t[5]+", "+t[6]+", "+t[7]+", "+t[8]+", "+t[9]+", "+t[10]+", "+t[11]+", "+t[12]+", "+t[13]+", "+t[14]+", "+t[15]+")"},n.frob=function(t){return Math.sqrt(Math.pow(t[0],2)+Math.pow(t[1],2)+Math.pow(t[2],2)+Math.pow(t[3],2)+Math.pow(t[4],2)+Math.pow(t[5],2)+Math.pow(t[6],2)+Math.pow(t[7],2)+Math.pow(t[8],2)+Math.pow(t[9],2)+Math.pow(t[10],2)+Math.pow(t[11],2)+Math.pow(t[12],2)+Math.pow(t[13],2)+Math.pow(t[14],2)+Math.pow(t[15],2))},n.add=function(t,n,r){return t[0]=n[0]+r[0],t[1]=n[1]+r[1],t[2]=n[2]+r[2],t[3]=n[3]+r[3],t[4]=n[4]+r[4],t[5]=n[5]+r[5],t[6]=n[6]+r[6],t[7]=n[7]+r[7],t[8]=n[8]+r[8],t[9]=n[9]+r[9],t[10]=n[10]+r[10],t[11]=n[11]+r[11],t[12]=n[12]+r[12],t[13]=n[13]+r[13],t[14]=n[14]+r[14],t[15]=n[15]+r[15],t},n.subtract=i,n.multiplyScalar=function(t,n,r){return t[0]=n[0]*r,t[1]=n[1]*r,t[2]=n[2]*r,t[3]=n[3]*r,t[4]=n[4]*r,t[5]=n[5]*r,t[6]=n[6]*r,t[7]=n[7]*r,t[8]=n[8]*r,t[9]=n[9]*r,t[10]=n[10]*r,t[11]=n[11]*r,t[12]=n[12]*r,t[13]=n[13]*r,t[14]=n[14]*r,t[15]=n[15]*r,t},n.multiplyScalarAndAdd=function(t,n,r,a){return t[0]=n[0]+r[0]*a,t[1]=n[1]+r[1]*a,t[2]=n[2]+r[2]*a,t[3]=n[3]+r[3]*a,t[4]=n[4]+r[4]*a,t[5]=n[5]+r[5]*a,t[6]=n[6]+r[6]*a,t[7]=n[7]+r[7]*a,t[8]=n[8]+r[8]*a,t[9]=n[9]+r[9]*a,t[10]=n[10]+r[10]*a,t[11]=n[11]+r[11]*a,t[12]=n[12]+r[12]*a,t[13]=n[13]+r[13]*a,t[14]=n[14]+r[14]*a,t[15]=n[15]+r[15]*a,t},n.exactEquals=function(t,n){return t[0]===n[0]&&t[1]===n[1]&&t[2]===n[2]&&t[3]===n[3]&&t[4]===n[4]&&t[5]===n[5]&&t[6]===n[6]&&t[7]===n[7]&&t[8]===n[8]&&t[9]===n[9]&&t[10]===n[10]&&t[11]===n[11]&&t[12]===n[12]&&t[13]===n[13]&&t[14]===n[14]&&t[15]===n[15]},n.equals=function(t,n){var r=t[0],e=t[1],u=t[2],o=t[3],i=t[4],s=t[5],c=t[6],f=t[7],M=t[8],h=t[9],l=t[10],v=t[11],d=t[12],b=t[13],m=t[14],p=t[15],P=n[0],A=n[1],E=n[2],O=n[3],R=n[4],y=n[5],q=n[6],x=n[7],_=n[8],Y=n[9],L=n[10],S=n[11],w=n[12],I=n[13],N=n[14],g=n[15];return Math.abs(r-P)<=a.EPSILON*Math.max(1,Math.abs(r),Math.abs(P))&&Math.abs(e-A)<=a.EPSILON*Math.max(1,Math.abs(e),Math.abs(A))&&Math.abs(u-E)<=a.EPSILON*Math.max(1,Math.abs(u),Math.abs(E))&&Math.abs(o-O)<=a.EPSILON*Math.max(1,Math.abs(o),Math.abs(O))&&Math.abs(i-R)<=a.EPSILON*Math.max(1,Math.abs(i),Math.abs(R))&&Math.abs(s-y)<=a.EPSILON*Math.max(1,Math.abs(s),Math.abs(y))&&Math.abs(c-q)<=a.EPSILON*Math.max(1,Math.abs(c),Math.abs(q))&&Math.abs(f-x)<=a.EPSILON*Math.max(1,Math.abs(f),Math.abs(x))&&Math.abs(M-_)<=a.EPSILON*Math.max(1,Math.abs(M),Math.abs(_))&&Math.abs(h-Y)<=a.EPSILON*Math.max(1,Math.abs(h),Math.abs(Y))&&Math.abs(l-L)<=a.EPSILON*Math.max(1,Math.abs(l),Math.abs(L))&&Math.abs(v-S)<=a.EPSILON*Math.max(1,Math.abs(v),Math.abs(S))&&Math.abs(d-w)<=a.EPSILON*Math.max(1,Math.abs(d),Math.abs(w))&&Math.abs(b-I)<=a.EPSILON*Math.max(1,Math.abs(b),Math.abs(I))&&Math.abs(m-N)<=a.EPSILON*Math.max(1,Math.abs(m),Math.abs(N))&&Math.abs(p-g)<=a.EPSILON*Math.max(1,Math.abs(p),Math.abs(g))};var a=function(t){if(t&&t.__esModule)return t;var n={};if(null!=t)for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(n[r]=t[r]);return n.default=t,n}(r(0));function e(t){return t[0]=1,t[1]=0,t[2]=0,t[3]=0,t[4]=0,t[5]=1,t[6]=0,t[7]=0,t[8]=0,t[9]=0,t[10]=1,t[11]=0,t[12]=0,t[13]=0,t[14]=0,t[15]=1,t}function u(t,n,r){var a=n[0],e=n[1],u=n[2],o=n[3],i=n[4],s=n[5],c=n[6],f=n[7],M=n[8],h=n[9],l=n[10],v=n[11],d=n[12],b=n[13],m=n[14],p=n[15],P=r[0],A=r[1],E=r[2],O=r[3];return t[0]=P*a+A*i+E*M+O*d,t[1]=P*e+A*s+E*h+O*b,t[2]=P*u+A*c+E*l+O*m,t[3]=P*o+A*f+E*v+O*p,P=r[4],A=r[5],E=r[6],O=r[7],t[4]=P*a+A*i+E*M+O*d,t[5]=P*e+A*s+E*h+O*b,t[6]=P*u+A*c+E*l+O*m,t[7]=P*o+A*f+E*v+O*p,P=r[8],A=r[9],E=r[10],O=r[11],t[8]=P*a+A*i+E*M+O*d,t[9]=P*e+A*s+E*h+O*b,t[10]=P*u+A*c+E*l+O*m,t[11]=P*o+A*f+E*v+O*p,P=r[12],A=r[13],E=r[14],O=r[15],t[12]=P*a+A*i+E*M+O*d,t[13]=P*e+A*s+E*h+O*b,t[14]=P*u+A*c+E*l+O*m,t[15]=P*o+A*f+E*v+O*p,t}function o(t,n,r){var a=n[0],e=n[1],u=n[2],o=n[3],i=a+a,s=e+e,c=u+u,f=a*i,M=a*s,h=a*c,l=e*s,v=e*c,d=u*c,b=o*i,m=o*s,p=o*c;return t[0]=1-(l+d),t[1]=M+p,t[2]=h-m,t[3]=0,t[4]=M-p,t[5]=1-(f+d),t[6]=v+b,t[7]=0,t[8]=h+m,t[9]=v-b,t[10]=1-(f+l),t[11]=0,t[12]=r[0],t[13]=r[1],t[14]=r[2],t[15]=1,t}function i(t,n,r){return t[0]=n[0]-r[0],t[1]=n[1]-r[1],t[2]=n[2]-r[2],t[3]=n[3]-r[3],t[4]=n[4]-r[4],t[5]=n[5]-r[5],t[6]=n[6]-r[6],t[7]=n[7]-r[7],t[8]=n[8]-r[8],t[9]=n[9]-r[9],t[10]=n[10]-r[10],t[11]=n[11]-r[11],t[12]=n[12]-r[12],t[13]=n[13]-r[13],t[14]=n[14]-r[14],t[15]=n[15]-r[15],t}n.mul=u,n.sub=i},function(t,n,r){"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.sub=n.mul=void 0,n.create=function(){var t=new a.ARRAY_TYPE(9);a.ARRAY_TYPE!=Float32Array&&(t[1]=0,t[2]=0,t[3]=0,t[5]=0,t[6]=0,t[7]=0);return t[0]=1,t[4]=1,t[8]=1,t},n.fromMat4=function(t,n){return t[0]=n[0],t[1]=n[1],t[2]=n[2],t[3]=n[4],t[4]=n[5],t[5]=n[6],t[6]=n[8],t[7]=n[9],t[8]=n[10],t},n.clone=function(t){var n=new a.ARRAY_TYPE(9);return n[0]=t[0],n[1]=t[1],n[2]=t[2],n[3]=t[3],n[4]=t[4],n[5]=t[5],n[6]=t[6],n[7]=t[7],n[8]=t[8],n},n.copy=function(t,n){return t[0]=n[0],t[1]=n[1],t[2]=n[2],t[3]=n[3],t[4]=n[4],t[5]=n[5],t[6]=n[6],t[7]=n[7],t[8]=n[8],t},n.fromValues=function(t,n,r,e,u,o,i,s,c){var f=new a.ARRAY_TYPE(9);return f[0]=t,f[1]=n,f[2]=r,f[3]=e,f[4]=u,f[5]=o,f[6]=i,f[7]=s,f[8]=c,f},n.set=function(t,n,r,a,e,u,o,i,s,c){return t[0]=n,t[1]=r,t[2]=a,t[3]=e,t[4]=u,t[5]=o,t[6]=i,t[7]=s,t[8]=c,t},n.identity=function(t){return t[0]=1,t[1]=0,t[2]=0,t[3]=0,t[4]=1,t[5]=0,t[6]=0,t[7]=0,t[8]=1,t},n.transpose=function(t,n){if(t===n){var r=n[1],a=n[2],e=n[5];t[1]=n[3],t[2]=n[6],t[3]=r,t[5]=n[7],t[6]=a,t[7]=e}else t[0]=n[0],t[1]=n[3],t[2]=n[6],t[3]=n[1],t[4]=n[4],t[5]=n[7],t[6]=n[2],t[7]=n[5],t[8]=n[8];return t},n.invert=function(t,n){var r=n[0],a=n[1],e=n[2],u=n[3],o=n[4],i=n[5],s=n[6],c=n[7],f=n[8],M=f*o-i*c,h=-f*u+i*s,l=c*u-o*s,v=r*M+a*h+e*l;if(!v)return null;return v=1/v,t[0]=M*v,t[1]=(-f*a+e*c)*v,t[2]=(i*a-e*o)*v,t[3]=h*v,t[4]=(f*r-e*s)*v,t[5]=(-i*r+e*u)*v,t[6]=l*v,t[7]=(-c*r+a*s)*v,t[8]=(o*r-a*u)*v,t},n.adjoint=function(t,n){var r=n[0],a=n[1],e=n[2],u=n[3],o=n[4],i=n[5],s=n[6],c=n[7],f=n[8];return t[0]=o*f-i*c,t[1]=e*c-a*f,t[2]=a*i-e*o,t[3]=i*s-u*f,t[4]=r*f-e*s,t[5]=e*u-r*i,t[6]=u*c-o*s,t[7]=a*s-r*c,t[8]=r*o-a*u,t},n.determinant=function(t){var n=t[0],r=t[1],a=t[2],e=t[3],u=t[4],o=t[5],i=t[6],s=t[7],c=t[8];return n*(c*u-o*s)+r*(-c*e+o*i)+a*(s*e-u*i)},n.multiply=e,n.translate=function(t,n,r){var a=n[0],e=n[1],u=n[2],o=n[3],i=n[4],s=n[5],c=n[6],f=n[7],M=n[8],h=r[0],l=r[1];return t[0]=a,t[1]=e,t[2]=u,t[3]=o,t[4]=i,t[5]=s,t[6]=h*a+l*o+c,t[7]=h*e+l*i+f,t[8]=h*u+l*s+M,t},n.rotate=function(t,n,r){var a=n[0],e=n[1],u=n[2],o=n[3],i=n[4],s=n[5],c=n[6],f=n[7],M=n[8],h=Math.sin(r),l=Math.cos(r);return t[0]=l*a+h*o,t[1]=l*e+h*i,t[2]=l*u+h*s,t[3]=l*o-h*a,t[4]=l*i-h*e,t[5]=l*s-h*u,t[6]=c,t[7]=f,t[8]=M,t},n.scale=function(t,n,r){var a=r[0],e=r[1];return t[0]=a*n[0],t[1]=a*n[1],t[2]=a*n[2],t[3]=e*n[3],t[4]=e*n[4],t[5]=e*n[5],t[6]=n[6],t[7]=n[7],t[8]=n[8],t},n.fromTranslation=function(t,n){return t[0]=1,t[1]=0,t[2]=0,t[3]=0,t[4]=1,t[5]=0,t[6]=n[0],t[7]=n[1],t[8]=1,t},n.fromRotation=function(t,n){var r=Math.sin(n),a=Math.cos(n);return t[0]=a,t[1]=r,t[2]=0,t[3]=-r,t[4]=a,t[5]=0,t[6]=0,t[7]=0,t[8]=1,t},n.fromScaling=function(t,n){return t[0]=n[0],t[1]=0,t[2]=0,t[3]=0,t[4]=n[1],t[5]=0,t[6]=0,t[7]=0,t[8]=1,t},n.fromMat2d=function(t,n){return t[0]=n[0],t[1]=n[1],t[2]=0,t[3]=n[2],t[4]=n[3],t[5]=0,t[6]=n[4],t[7]=n[5],t[8]=1,t},n.fromQuat=function(t,n){var r=n[0],a=n[1],e=n[2],u=n[3],o=r+r,i=a+a,s=e+e,c=r*o,f=a*o,M=a*i,h=e*o,l=e*i,v=e*s,d=u*o,b=u*i,m=u*s;return t[0]=1-M-v,t[3]=f-m,t[6]=h+b,t[1]=f+m,t[4]=1-c-v,t[7]=l-d,t[2]=h-b,t[5]=l+d,t[8]=1-c-M,t},n.normalFromMat4=function(t,n){var r=n[0],a=n[1],e=n[2],u=n[3],o=n[4],i=n[5],s=n[6],c=n[7],f=n[8],M=n[9],h=n[10],l=n[11],v=n[12],d=n[13],b=n[14],m=n[15],p=r*i-a*o,P=r*s-e*o,A=r*c-u*o,E=a*s-e*i,O=a*c-u*i,R=e*c-u*s,y=f*d-M*v,q=f*b-h*v,x=f*m-l*v,_=M*b-h*d,Y=M*m-l*d,L=h*m-l*b,S=p*L-P*Y+A*_+E*x-O*q+R*y;if(!S)return null;return S=1/S,t[0]=(i*L-s*Y+c*_)*S,t[1]=(s*x-o*L-c*q)*S,t[2]=(o*Y-i*x+c*y)*S,t[3]=(e*Y-a*L-u*_)*S,t[4]=(r*L-e*x+u*q)*S,t[5]=(a*x-r*Y-u*y)*S,t[6]=(d*R-b*O+m*E)*S,t[7]=(b*A-v*R-m*P)*S,t[8]=(v*O-d*A+m*p)*S,t},n.projection=function(t,n,r){return t[0]=2/n,t[1]=0,t[2]=0,t[3]=0,t[4]=-2/r,t[5]=0,t[6]=-1,t[7]=1,t[8]=1,t},n.str=function(t){return"mat3("+t[0]+", "+t[1]+", "+t[2]+", "+t[3]+", "+t[4]+", "+t[5]+", "+t[6]+", "+t[7]+", "+t[8]+")"},n.frob=function(t){return Math.sqrt(Math.pow(t[0],2)+Math.pow(t[1],2)+Math.pow(t[2],2)+Math.pow(t[3],2)+Math.pow(t[4],2)+Math.pow(t[5],2)+Math.pow(t[6],2)+Math.pow(t[7],2)+Math.pow(t[8],2))},n.add=function(t,n,r){return t[0]=n[0]+r[0],t[1]=n[1]+r[1],t[2]=n[2]+r[2],t[3]=n[3]+r[3],t[4]=n[4]+r[4],t[5]=n[5]+r[5],t[6]=n[6]+r[6],t[7]=n[7]+r[7],t[8]=n[8]+r[8],t},n.subtract=u,n.multiplyScalar=function(t,n,r){return t[0]=n[0]*r,t[1]=n[1]*r,t[2]=n[2]*r,t[3]=n[3]*r,t[4]=n[4]*r,t[5]=n[5]*r,t[6]=n[6]*r,t[7]=n[7]*r,t[8]=n[8]*r,t},n.multiplyScalarAndAdd=function(t,n,r,a){return t[0]=n[0]+r[0]*a,t[1]=n[1]+r[1]*a,t[2]=n[2]+r[2]*a,t[3]=n[3]+r[3]*a,t[4]=n[4]+r[4]*a,t[5]=n[5]+r[5]*a,t[6]=n[6]+r[6]*a,t[7]=n[7]+r[7]*a,t[8]=n[8]+r[8]*a,t},n.exactEquals=function(t,n){return t[0]===n[0]&&t[1]===n[1]&&t[2]===n[2]&&t[3]===n[3]&&t[4]===n[4]&&t[5]===n[5]&&t[6]===n[6]&&t[7]===n[7]&&t[8]===n[8]},n.equals=function(t,n){var r=t[0],e=t[1],u=t[2],o=t[3],i=t[4],s=t[5],c=t[6],f=t[7],M=t[8],h=n[0],l=n[1],v=n[2],d=n[3],b=n[4],m=n[5],p=n[6],P=n[7],A=n[8];return Math.abs(r-h)<=a.EPSILON*Math.max(1,Math.abs(r),Math.abs(h))&&Math.abs(e-l)<=a.EPSILON*Math.max(1,Math.abs(e),Math.abs(l))&&Math.abs(u-v)<=a.EPSILON*Math.max(1,Math.abs(u),Math.abs(v))&&Math.abs(o-d)<=a.EPSILON*Math.max(1,Math.abs(o),Math.abs(d))&&Math.abs(i-b)<=a.EPSILON*Math.max(1,Math.abs(i),Math.abs(b))&&Math.abs(s-m)<=a.EPSILON*Math.max(1,Math.abs(s),Math.abs(m))&&Math.abs(c-p)<=a.EPSILON*Math.max(1,Math.abs(c),Math.abs(p))&&Math.abs(f-P)<=a.EPSILON*Math.max(1,Math.abs(f),Math.abs(P))&&Math.abs(M-A)<=a.EPSILON*Math.max(1,Math.abs(M),Math.abs(A))};var a=function(t){if(t&&t.__esModule)return t;var n={};if(null!=t)for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(n[r]=t[r]);return n.default=t,n}(r(0));function e(t,n,r){var a=n[0],e=n[1],u=n[2],o=n[3],i=n[4],s=n[5],c=n[6],f=n[7],M=n[8],h=r[0],l=r[1],v=r[2],d=r[3],b=r[4],m=r[5],p=r[6],P=r[7],A=r[8];return t[0]=h*a+l*o+v*c,t[1]=h*e+l*i+v*f,t[2]=h*u+l*s+v*M,t[3]=d*a+b*o+m*c,t[4]=d*e+b*i+m*f,t[5]=d*u+b*s+m*M,t[6]=p*a+P*o+A*c,t[7]=p*e+P*i+A*f,t[8]=p*u+P*s+A*M,t}function u(t,n,r){return t[0]=n[0]-r[0],t[1]=n[1]-r[1],t[2]=n[2]-r[2],t[3]=n[3]-r[3],t[4]=n[4]-r[4],t[5]=n[5]-r[5],t[6]=n[6]-r[6],t[7]=n[7]-r[7],t[8]=n[8]-r[8],t}n.mul=e,n.sub=u},function(t,n,r){"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.forEach=n.sqrLen=n.sqrDist=n.dist=n.div=n.mul=n.sub=n.len=void 0,n.create=e,n.clone=function(t){var n=new a.ARRAY_TYPE(2);return n[0]=t[0],n[1]=t[1],n},n.fromValues=function(t,n){var r=new a.ARRAY_TYPE(2);return r[0]=t,r[1]=n,r},n.copy=function(t,n){return t[0]=n[0],t[1]=n[1],t},n.set=function(t,n,r){return t[0]=n,t[1]=r,t},n.add=function(t,n,r){return t[0]=n[0]+r[0],t[1]=n[1]+r[1],t},n.subtract=u,n.multiply=o,n.divide=i,n.ceil=function(t,n){return t[0]=Math.ceil(n[0]),t[1]=Math.ceil(n[1]),t},n.floor=function(t,n){return t[0]=Math.floor(n[0]),t[1]=Math.floor(n[1]),t},n.min=function(t,n,r){return t[0]=Math.min(n[0],r[0]),t[1]=Math.min(n[1],r[1]),t},n.max=function(t,n,r){return t[0]=Math.max(n[0],r[0]),t[1]=Math.max(n[1],r[1]),t},n.round=function(t,n){return t[0]=Math.round(n[0]),t[1]=Math.round(n[1]),t},n.scale=function(t,n,r){return t[0]=n[0]*r,t[1]=n[1]*r,t},n.scaleAndAdd=function(t,n,r,a){return t[0]=n[0]+r[0]*a,t[1]=n[1]+r[1]*a,t},n.distance=s,n.squaredDistance=c,n.length=f,n.squaredLength=M,n.negate=function(t,n){return t[0]=-n[0],t[1]=-n[1],t},n.inverse=function(t,n){return t[0]=1/n[0],t[1]=1/n[1],t},n.normalize=function(t,n){var r=n[0],a=n[1],e=r*r+a*a;e>0&&(e=1/Math.sqrt(e),t[0]=n[0]*e,t[1]=n[1]*e);return t},n.dot=function(t,n){return t[0]*n[0]+t[1]*n[1]},n.cross=function(t,n,r){var a=n[0]*r[1]-n[1]*r[0];return t[0]=t[1]=0,t[2]=a,t},n.lerp=function(t,n,r,a){var e=n[0],u=n[1];return t[0]=e+a*(r[0]-e),t[1]=u+a*(r[1]-u),t},n.random=function(t,n){n=n||1;var r=2*a.RANDOM()*Math.PI;return t[0]=Math.cos(r)*n,t[1]=Math.sin(r)*n,t},n.transformMat2=function(t,n,r){var a=n[0],e=n[1];return t[0]=r[0]*a+r[2]*e,t[1]=r[1]*a+r[3]*e,t},n.transformMat2d=function(t,n,r){var a=n[0],e=n[1];return t[0]=r[0]*a+r[2]*e+r[4],t[1]=r[1]*a+r[3]*e+r[5],t},n.transformMat3=function(t,n,r){var a=n[0],e=n[1];return t[0]=r[0]*a+r[3]*e+r[6],t[1]=r[1]*a+r[4]*e+r[7],t},n.transformMat4=function(t,n,r){var a=n[0],e=n[1];return t[0]=r[0]*a+r[4]*e+r[12],t[1]=r[1]*a+r[5]*e+r[13],t},n.rotate=function(t,n,r,a){var e=n[0]-r[0],u=n[1]-r[1],o=Math.sin(a),i=Math.cos(a);return t[0]=e*i-u*o+r[0],t[1]=e*o+u*i+r[1],t},n.angle=function(t,n){var r=t[0],a=t[1],e=n[0],u=n[1],o=r*r+a*a;o>0&&(o=1/Math.sqrt(o));var i=e*e+u*u;i>0&&(i=1/Math.sqrt(i));var s=(r*e+a*u)*o*i;return s>1?0:s<-1?Math.PI:Math.acos(s)},n.str=function(t){return"vec2("+t[0]+", "+t[1]+")"},n.exactEquals=function(t,n){return t[0]===n[0]&&t[1]===n[1]},n.equals=function(t,n){var r=t[0],e=t[1],u=n[0],o=n[1];return Math.abs(r-u)<=a.EPSILON*Math.max(1,Math.abs(r),Math.abs(u))&&Math.abs(e-o)<=a.EPSILON*Math.max(1,Math.abs(e),Math.abs(o))};var a=function(t){if(t&&t.__esModule)return t;var n={};if(null!=t)for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(n[r]=t[r]);return n.default=t,n}(r(0));function e(){var t=new a.ARRAY_TYPE(2);return a.ARRAY_TYPE!=Float32Array&&(t[0]=0,t[1]=0),t}function u(t,n,r){return t[0]=n[0]-r[0],t[1]=n[1]-r[1],t}function o(t,n,r){return t[0]=n[0]*r[0],t[1]=n[1]*r[1],t}function i(t,n,r){return t[0]=n[0]/r[0],t[1]=n[1]/r[1],t}function s(t,n){var r=n[0]-t[0],a=n[1]-t[1];return Math.sqrt(r*r+a*a)}function c(t,n){var r=n[0]-t[0],a=n[1]-t[1];return r*r+a*a}function f(t){var n=t[0],r=t[1];return Math.sqrt(n*n+r*r)}function M(t){var n=t[0],r=t[1];return n*n+r*r}n.len=f,n.sub=u,n.mul=o,n.div=i,n.dist=s,n.sqrDist=c,n.sqrLen=M,n.forEach=function(){var t=e();return function(n,r,a,e,u,o){var i=void 0,s=void 0;for(r||(r=2),a||(a=0),s=e?Math.min(e*r+a,n.length):n.length,i=a;i0){r=Math.sqrt(r);var a=n[0]/r,e=n[1]/r,u=n[2]/r,o=n[3]/r,i=n[4],s=n[5],c=n[6],f=n[7],M=a*i+e*s+u*c+o*f;t[0]=a,t[1]=e,t[2]=u,t[3]=o,t[4]=(i-a*M)/r,t[5]=(s-e*M)/r,t[6]=(c-u*M)/r,t[7]=(f-o*M)/r}return t},n.str=function(t){return"quat2("+t[0]+", "+t[1]+", "+t[2]+", "+t[3]+", "+t[4]+", "+t[5]+", "+t[6]+", "+t[7]+")"},n.exactEquals=function(t,n){return t[0]===n[0]&&t[1]===n[1]&&t[2]===n[2]&&t[3]===n[3]&&t[4]===n[4]&&t[5]===n[5]&&t[6]===n[6]&&t[7]===n[7]},n.equals=function(t,n){var r=t[0],e=t[1],u=t[2],o=t[3],i=t[4],s=t[5],c=t[6],f=t[7],M=n[0],h=n[1],l=n[2],v=n[3],d=n[4],b=n[5],m=n[6],p=n[7];return Math.abs(r-M)<=a.EPSILON*Math.max(1,Math.abs(r),Math.abs(M))&&Math.abs(e-h)<=a.EPSILON*Math.max(1,Math.abs(e),Math.abs(h))&&Math.abs(u-l)<=a.EPSILON*Math.max(1,Math.abs(u),Math.abs(l))&&Math.abs(o-v)<=a.EPSILON*Math.max(1,Math.abs(o),Math.abs(v))&&Math.abs(i-d)<=a.EPSILON*Math.max(1,Math.abs(i),Math.abs(d))&&Math.abs(s-b)<=a.EPSILON*Math.max(1,Math.abs(s),Math.abs(b))&&Math.abs(c-m)<=a.EPSILON*Math.max(1,Math.abs(c),Math.abs(m))&&Math.abs(f-p)<=a.EPSILON*Math.max(1,Math.abs(f),Math.abs(p))};var a=o(r(0)),e=o(r(3)),u=o(r(4));function o(t){if(t&&t.__esModule)return t;var n={};if(null!=t)for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(n[r]=t[r]);return n.default=t,n}function i(t,n,r){var a=.5*r[0],e=.5*r[1],u=.5*r[2],o=n[0],i=n[1],s=n[2],c=n[3];return t[0]=o,t[1]=i,t[2]=s,t[3]=c,t[4]=a*c+e*s-u*i,t[5]=e*c+u*o-a*s,t[6]=u*c+a*i-e*o,t[7]=-a*o-e*i-u*s,t}function s(t,n){return t[0]=n[0],t[1]=n[1],t[2]=n[2],t[3]=n[3],t[4]=n[4],t[5]=n[5],t[6]=n[6],t[7]=n[7],t}n.getReal=e.copy;n.setReal=e.copy;function c(t,n,r){var a=n[0],e=n[1],u=n[2],o=n[3],i=r[4],s=r[5],c=r[6],f=r[7],M=n[4],h=n[5],l=n[6],v=n[7],d=r[0],b=r[1],m=r[2],p=r[3];return t[0]=a*p+o*d+e*m-u*b,t[1]=e*p+o*b+u*d-a*m,t[2]=u*p+o*m+a*b-e*d,t[3]=o*p-a*d-e*b-u*m,t[4]=a*f+o*i+e*c-u*s+M*p+v*d+h*m-l*b,t[5]=e*f+o*s+u*i-a*c+h*p+v*b+l*d-M*m,t[6]=u*f+o*c+a*s-e*i+l*p+v*m+M*b-h*d,t[7]=o*f-a*i-e*s-u*c+v*p-M*d-h*b-l*m,t}n.mul=c;var f=n.dot=e.dot;var M=n.length=e.length,h=(n.len=M,n.squaredLength=e.squaredLength);n.sqrLen=h},function(t,n,r){"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.sub=n.mul=void 0,n.create=function(){var t=new a.ARRAY_TYPE(6);a.ARRAY_TYPE!=Float32Array&&(t[1]=0,t[2]=0,t[4]=0,t[5]=0);return t[0]=1,t[3]=1,t},n.clone=function(t){var n=new a.ARRAY_TYPE(6);return n[0]=t[0],n[1]=t[1],n[2]=t[2],n[3]=t[3],n[4]=t[4],n[5]=t[5],n},n.copy=function(t,n){return t[0]=n[0],t[1]=n[1],t[2]=n[2],t[3]=n[3],t[4]=n[4],t[5]=n[5],t},n.identity=function(t){return t[0]=1,t[1]=0,t[2]=0,t[3]=1,t[4]=0,t[5]=0,t},n.fromValues=function(t,n,r,e,u,o){var i=new a.ARRAY_TYPE(6);return i[0]=t,i[1]=n,i[2]=r,i[3]=e,i[4]=u,i[5]=o,i},n.set=function(t,n,r,a,e,u,o){return t[0]=n,t[1]=r,t[2]=a,t[3]=e,t[4]=u,t[5]=o,t},n.invert=function(t,n){var r=n[0],a=n[1],e=n[2],u=n[3],o=n[4],i=n[5],s=r*u-a*e;if(!s)return null;return s=1/s,t[0]=u*s,t[1]=-a*s,t[2]=-e*s,t[3]=r*s,t[4]=(e*i-u*o)*s,t[5]=(a*o-r*i)*s,t},n.determinant=function(t){return t[0]*t[3]-t[1]*t[2]},n.multiply=e,n.rotate=function(t,n,r){var a=n[0],e=n[1],u=n[2],o=n[3],i=n[4],s=n[5],c=Math.sin(r),f=Math.cos(r);return t[0]=a*f+u*c,t[1]=e*f+o*c,t[2]=a*-c+u*f,t[3]=e*-c+o*f,t[4]=i,t[5]=s,t},n.scale=function(t,n,r){var a=n[0],e=n[1],u=n[2],o=n[3],i=n[4],s=n[5],c=r[0],f=r[1];return t[0]=a*c,t[1]=e*c,t[2]=u*f,t[3]=o*f,t[4]=i,t[5]=s,t},n.translate=function(t,n,r){var a=n[0],e=n[1],u=n[2],o=n[3],i=n[4],s=n[5],c=r[0],f=r[1];return t[0]=a,t[1]=e,t[2]=u,t[3]=o,t[4]=a*c+u*f+i,t[5]=e*c+o*f+s,t},n.fromRotation=function(t,n){var r=Math.sin(n),a=Math.cos(n);return t[0]=a,t[1]=r,t[2]=-r,t[3]=a,t[4]=0,t[5]=0,t},n.fromScaling=function(t,n){return t[0]=n[0],t[1]=0,t[2]=0,t[3]=n[1],t[4]=0,t[5]=0,t},n.fromTranslation=function(t,n){return t[0]=1,t[1]=0,t[2]=0,t[3]=1,t[4]=n[0],t[5]=n[1],t},n.str=function(t){return"mat2d("+t[0]+", "+t[1]+", "+t[2]+", "+t[3]+", "+t[4]+", "+t[5]+")"},n.frob=function(t){return Math.sqrt(Math.pow(t[0],2)+Math.pow(t[1],2)+Math.pow(t[2],2)+Math.pow(t[3],2)+Math.pow(t[4],2)+Math.pow(t[5],2)+1)},n.add=function(t,n,r){return t[0]=n[0]+r[0],t[1]=n[1]+r[1],t[2]=n[2]+r[2],t[3]=n[3]+r[3],t[4]=n[4]+r[4],t[5]=n[5]+r[5],t},n.subtract=u,n.multiplyScalar=function(t,n,r){return t[0]=n[0]*r,t[1]=n[1]*r,t[2]=n[2]*r,t[3]=n[3]*r,t[4]=n[4]*r,t[5]=n[5]*r,t},n.multiplyScalarAndAdd=function(t,n,r,a){return t[0]=n[0]+r[0]*a,t[1]=n[1]+r[1]*a,t[2]=n[2]+r[2]*a,t[3]=n[3]+r[3]*a,t[4]=n[4]+r[4]*a,t[5]=n[5]+r[5]*a,t},n.exactEquals=function(t,n){return t[0]===n[0]&&t[1]===n[1]&&t[2]===n[2]&&t[3]===n[3]&&t[4]===n[4]&&t[5]===n[5]},n.equals=function(t,n){var r=t[0],e=t[1],u=t[2],o=t[3],i=t[4],s=t[5],c=n[0],f=n[1],M=n[2],h=n[3],l=n[4],v=n[5];return Math.abs(r-c)<=a.EPSILON*Math.max(1,Math.abs(r),Math.abs(c))&&Math.abs(e-f)<=a.EPSILON*Math.max(1,Math.abs(e),Math.abs(f))&&Math.abs(u-M)<=a.EPSILON*Math.max(1,Math.abs(u),Math.abs(M))&&Math.abs(o-h)<=a.EPSILON*Math.max(1,Math.abs(o),Math.abs(h))&&Math.abs(i-l)<=a.EPSILON*Math.max(1,Math.abs(i),Math.abs(l))&&Math.abs(s-v)<=a.EPSILON*Math.max(1,Math.abs(s),Math.abs(v))};var a=function(t){if(t&&t.__esModule)return t;var n={};if(null!=t)for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(n[r]=t[r]);return n.default=t,n}(r(0));function e(t,n,r){var a=n[0],e=n[1],u=n[2],o=n[3],i=n[4],s=n[5],c=r[0],f=r[1],M=r[2],h=r[3],l=r[4],v=r[5];return t[0]=a*c+u*f,t[1]=e*c+o*f,t[2]=a*M+u*h,t[3]=e*M+o*h,t[4]=a*l+u*v+i,t[5]=e*l+o*v+s,t}function u(t,n,r){return t[0]=n[0]-r[0],t[1]=n[1]-r[1],t[2]=n[2]-r[2],t[3]=n[3]-r[3],t[4]=n[4]-r[4],t[5]=n[5]-r[5],t}n.mul=e,n.sub=u},function(t,n,r){"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.sub=n.mul=void 0,n.create=function(){var t=new a.ARRAY_TYPE(4);a.ARRAY_TYPE!=Float32Array&&(t[1]=0,t[2]=0);return t[0]=1,t[3]=1,t},n.clone=function(t){var n=new a.ARRAY_TYPE(4);return n[0]=t[0],n[1]=t[1],n[2]=t[2],n[3]=t[3],n},n.copy=function(t,n){return t[0]=n[0],t[1]=n[1],t[2]=n[2],t[3]=n[3],t},n.identity=function(t){return t[0]=1,t[1]=0,t[2]=0,t[3]=1,t},n.fromValues=function(t,n,r,e){var u=new a.ARRAY_TYPE(4);return u[0]=t,u[1]=n,u[2]=r,u[3]=e,u},n.set=function(t,n,r,a,e){return t[0]=n,t[1]=r,t[2]=a,t[3]=e,t},n.transpose=function(t,n){if(t===n){var r=n[1];t[1]=n[2],t[2]=r}else t[0]=n[0],t[1]=n[2],t[2]=n[1],t[3]=n[3];return t},n.invert=function(t,n){var r=n[0],a=n[1],e=n[2],u=n[3],o=r*u-e*a;if(!o)return null;return o=1/o,t[0]=u*o,t[1]=-a*o,t[2]=-e*o,t[3]=r*o,t},n.adjoint=function(t,n){var r=n[0];return t[0]=n[3],t[1]=-n[1],t[2]=-n[2],t[3]=r,t},n.determinant=function(t){return t[0]*t[3]-t[2]*t[1]},n.multiply=e,n.rotate=function(t,n,r){var a=n[0],e=n[1],u=n[2],o=n[3],i=Math.sin(r),s=Math.cos(r);return t[0]=a*s+u*i,t[1]=e*s+o*i,t[2]=a*-i+u*s,t[3]=e*-i+o*s,t},n.scale=function(t,n,r){var a=n[0],e=n[1],u=n[2],o=n[3],i=r[0],s=r[1];return t[0]=a*i,t[1]=e*i,t[2]=u*s,t[3]=o*s,t},n.fromRotation=function(t,n){var r=Math.sin(n),a=Math.cos(n);return t[0]=a,t[1]=r,t[2]=-r,t[3]=a,t},n.fromScaling=function(t,n){return t[0]=n[0],t[1]=0,t[2]=0,t[3]=n[1],t},n.str=function(t){return"mat2("+t[0]+", "+t[1]+", "+t[2]+", "+t[3]+")"},n.frob=function(t){return Math.sqrt(Math.pow(t[0],2)+Math.pow(t[1],2)+Math.pow(t[2],2)+Math.pow(t[3],2))},n.LDU=function(t,n,r,a){return t[2]=a[2]/a[0],r[0]=a[0],r[1]=a[1],r[3]=a[3]-t[2]*r[1],[t,n,r]},n.add=function(t,n,r){return t[0]=n[0]+r[0],t[1]=n[1]+r[1],t[2]=n[2]+r[2],t[3]=n[3]+r[3],t},n.subtract=u,n.exactEquals=function(t,n){return t[0]===n[0]&&t[1]===n[1]&&t[2]===n[2]&&t[3]===n[3]},n.equals=function(t,n){var r=t[0],e=t[1],u=t[2],o=t[3],i=n[0],s=n[1],c=n[2],f=n[3];return Math.abs(r-i)<=a.EPSILON*Math.max(1,Math.abs(r),Math.abs(i))&&Math.abs(e-s)<=a.EPSILON*Math.max(1,Math.abs(e),Math.abs(s))&&Math.abs(u-c)<=a.EPSILON*Math.max(1,Math.abs(u),Math.abs(c))&&Math.abs(o-f)<=a.EPSILON*Math.max(1,Math.abs(o),Math.abs(f))},n.multiplyScalar=function(t,n,r){return t[0]=n[0]*r,t[1]=n[1]*r,t[2]=n[2]*r,t[3]=n[3]*r,t},n.multiplyScalarAndAdd=function(t,n,r,a){return t[0]=n[0]+r[0]*a,t[1]=n[1]+r[1]*a,t[2]=n[2]+r[2]*a,t[3]=n[3]+r[3]*a,t};var a=function(t){if(t&&t.__esModule)return t;var n={};if(null!=t)for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(n[r]=t[r]);return n.default=t,n}(r(0));function e(t,n,r){var a=n[0],e=n[1],u=n[2],o=n[3],i=r[0],s=r[1],c=r[2],f=r[3];return t[0]=a*i+u*s,t[1]=e*i+o*s,t[2]=a*c+u*f,t[3]=e*c+o*f,t}function u(t,n,r){return t[0]=n[0]-r[0],t[1]=n[1]-r[1],t[2]=n[2]-r[2],t[3]=n[3]-r[3],t}n.mul=e,n.sub=u},function(t,n,r){"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.vec4=n.vec3=n.vec2=n.quat2=n.quat=n.mat4=n.mat3=n.mat2d=n.mat2=n.glMatrix=void 0;var a=l(r(0)),e=l(r(9)),u=l(r(8)),o=l(r(5)),i=l(r(4)),s=l(r(3)),c=l(r(7)),f=l(r(6)),M=l(r(2)),h=l(r(1));function l(t){if(t&&t.__esModule)return t;var n={};if(null!=t)for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(n[r]=t[r]);return n.default=t,n}n.glMatrix=a,n.mat2=e,n.mat2d=u,n.mat3=o,n.mat4=i,n.quat=s,n.quat2=c,n.vec2=f,n.vec3=M,n.vec4=h}])}); \ No newline at end of file diff --git a/editor/js/libs/litegl.js b/editor/js/libs/litegl.js new file mode 100644 index 000000000..f9ae7f822 --- /dev/null +++ b/editor/js/libs/litegl.js @@ -0,0 +1,13432 @@ +//packer version +//litegl.js by Javi Agenjo 2014 @tamat (tamats.com) +//forked from lightgl.js by Evan Wallace (madebyevan.com) +"use strict"; + +(function(global){ + +var GL = global.GL = {}; + +if(typeof(glMatrix) == "undefined") + throw("litegl.js requires gl-matrix to work. It must be included before litegl."); +else +{ + if(!global.vec2) + throw("litegl.js does not support gl-matrix 3.0, download 2.8 https://github.com/toji/gl-matrix/releases/tag/v2.8.1"); +} + +//polyfill +global.requestAnimationFrame = global.requestAnimationFrame || global.mozRequestAnimationFrame || global.webkitRequestAnimationFrame || function(callback) { setTimeout(callback, 1000 / 60); }; + +GL.blockable_keys = {"Up":true,"Down":true,"Left":true,"Right":true}; + +GL.reverse = null; + +//some consts +//https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button +GL.LEFT_MOUSE_BUTTON = 0; +GL.MIDDLE_MOUSE_BUTTON = 1; +GL.RIGHT_MOUSE_BUTTON = 2; + +GL.LEFT_MOUSE_BUTTON_MASK = 1; +GL.RIGHT_MOUSE_BUTTON_MASK = 2; +GL.MIDDLE_MOUSE_BUTTON_MASK = 4; + +GL.last_context_id = 0; + + +//Define WEBGL ENUMS as statics (more to come in WebGL 2) +//sometimes we need some gl enums before having the gl context, solution: define them globally because the specs says they are constant) + +GL.COLOR_BUFFER_BIT = 16384; +GL.DEPTH_BUFFER_BIT = 256; +GL.STENCIL_BUFFER_BIT = 1024; + +GL.TEXTURE_2D = 3553; +GL.TEXTURE_CUBE_MAP = 34067; +GL.TEXTURE_3D = 32879; + +GL.TEXTURE_MAG_FILTER = 10240; +GL.TEXTURE_MIN_FILTER = 10241; +GL.TEXTURE_WRAP_S = 10242; +GL.TEXTURE_WRAP_T = 10243; + +GL.BYTE = 5120; +GL.UNSIGNED_BYTE = 5121; +GL.SHORT = 5122; +GL.UNSIGNED_SHORT = 5123; +GL.INT = 5124; +GL.UNSIGNED_INT = 5125; +GL.FLOAT = 5126; +GL.HALF_FLOAT_OES = 36193; //webgl 1.0 only + +//webgl2 formats +GL.HALF_FLOAT = 5131; +GL.DEPTH_COMPONENT16 = 33189; +GL.DEPTH_COMPONENT24 = 33190; +GL.DEPTH_COMPONENT32F = 36012; + +GL.FLOAT_VEC2 = 35664; +GL.FLOAT_VEC3 = 35665; +GL.FLOAT_VEC4 = 35666; +GL.INT_VEC2 = 35667; +GL.INT_VEC3 = 35668; +GL.INT_VEC4 = 35669; +GL.BOOL = 35670; +GL.BOOL_VEC2 = 35671; +GL.BOOL_VEC3 = 35672; +GL.BOOL_VEC4 = 35673; +GL.FLOAT_MAT2 = 35674; +GL.FLOAT_MAT3 = 35675; +GL.FLOAT_MAT4 = 35676; + +//used to know the amount of data to reserve per uniform +GL.TYPE_LENGTH = {}; +GL.TYPE_LENGTH[ GL.FLOAT ] = GL.TYPE_LENGTH[ GL.INT ] = GL.TYPE_LENGTH[ GL.BYTE ] = GL.TYPE_LENGTH[ GL.BOOL ] = 1; +GL.TYPE_LENGTH[ GL.FLOAT_VEC2 ] = GL.TYPE_LENGTH[ GL.INT_VEC2 ] = GL.TYPE_LENGTH[ GL.BOOL_VEC2 ] = 2; +GL.TYPE_LENGTH[ GL.FLOAT_VEC3 ] = GL.TYPE_LENGTH[ GL.INT_VEC3 ] = GL.TYPE_LENGTH[ GL.BOOL_VEC3 ] = 3; +GL.TYPE_LENGTH[ GL.FLOAT_VEC4 ] = GL.TYPE_LENGTH[ GL.INT_VEC4 ] = GL.TYPE_LENGTH[ GL.BOOL_VEC4 ] = 4; +GL.TYPE_LENGTH[ GL.FLOAT_MAT3 ] = 9; +GL.TYPE_LENGTH[ GL.FLOAT_MAT4 ] = 16; + + +GL.SAMPLER_2D = 35678; +GL.SAMPLER_3D = 35679; +GL.SAMPLER_CUBE = 35680; + +GL.DEPTH_COMPONENT = 6402; +GL.ALPHA = 6406; +GL.RGB = 6407; +GL.RGBA = 6408; +GL.LUMINANCE = 6409; +GL.LUMINANCE_ALPHA = 6410; +GL.DEPTH_STENCIL = 34041; +GL.UNSIGNED_INT_24_8_WEBGL = 34042; + +//webgl2 formats +GL.R8 = 33321; +GL.R16F = 33325; +GL.R32F = 33326; +GL.R8UI = 33330; +GL.RG8 = 33323; +GL.RG16F = 33327; +GL.RG32F = 33328; +GL.RGB8 = 32849; +GL.SRGB8 = 35905; +GL.RGB565 = 36194; +GL.R11F_G11F_B10F = 35898; +GL.RGB9_E5 = 35901; +GL.RGB16F = 34843; +GL.RGB32F = 34837; +GL.RGB8UI = 36221; +GL.RGBA8 = 32856; +GL.RGB5_A1 = 32855; +GL.RGBA16F = 34842; +GL.RGBA32F = 34836; +GL.RGBA8UI = 36220; +GL.RGBA16I = 36232; +GL.RGBA16UI = 36214; +GL.RGBA32I = 36226; +GL.RGBA32UI = 36208; + +GL.NEAREST = 9728; +GL.LINEAR = 9729; +GL.NEAREST_MIPMAP_NEAREST = 9984; +GL.LINEAR_MIPMAP_NEAREST = 9985; +GL.NEAREST_MIPMAP_LINEAR = 9986; +GL.LINEAR_MIPMAP_LINEAR = 9987; + +GL.REPEAT = 10497; +GL.CLAMP_TO_EDGE = 33071; +GL.MIRRORED_REPEAT = 33648; + +GL.ZERO = 0; +GL.ONE = 1; +GL.SRC_COLOR = 768; +GL.ONE_MINUS_SRC_COLOR = 769; +GL.SRC_ALPHA = 770; +GL.ONE_MINUS_SRC_ALPHA = 771; +GL.DST_ALPHA = 772; +GL.ONE_MINUS_DST_ALPHA = 773; +GL.DST_COLOR = 774; +GL.ONE_MINUS_DST_COLOR = 775; +GL.SRC_ALPHA_SATURATE = 776; +GL.CONSTANT_COLOR = 32769; +GL.ONE_MINUS_CONSTANT_COLOR = 32770; +GL.CONSTANT_ALPHA = 32771; +GL.ONE_MINUS_CONSTANT_ALPHA = 32772; + +GL.VERTEX_SHADER = 35633; +GL.FRAGMENT_SHADER = 35632; + +GL.FRONT = 1028; +GL.BACK = 1029; +GL.FRONT_AND_BACK = 1032; + +GL.NEVER = 512; +GL.LESS = 513; +GL.EQUAL = 514; +GL.LEQUAL = 515; +GL.GREATER = 516; +GL.NOTEQUAL = 517; +GL.GEQUAL = 518; +GL.ALWAYS = 519; + +GL.KEEP = 7680; +GL.REPLACE = 7681; +GL.INCR = 7682; +GL.DECR = 7683; +GL.INCR_WRAP = 34055; +GL.DECR_WRAP = 34056; +GL.INVERT = 5386; + +GL.STREAM_DRAW = 35040; +GL.STATIC_DRAW = 35044; +GL.DYNAMIC_DRAW = 35048; + +GL.ARRAY_BUFFER = 34962; +GL.ELEMENT_ARRAY_BUFFER = 34963; + +GL.POINTS = 0; +GL.LINES = 1; +GL.LINE_LOOP = 2; +GL.LINE_STRIP = 3; +GL.TRIANGLES = 4; +GL.TRIANGLE_STRIP = 5; +GL.TRIANGLE_FAN = 6; + +GL.CW = 2304; +GL.CCW = 2305; + +GL.CULL_FACE = 2884; +GL.DEPTH_TEST = 2929; +GL.BLEND = 3042; + +GL.temp_vec3 = vec3.create(); +GL.temp2_vec3 = vec3.create(); +GL.temp_vec4 = vec4.create(); +GL.temp_quat = quat.create(); +GL.temp_mat3 = mat3.create(); +GL.temp_mat4 = mat4.create(); + + +global.DEG2RAD = 0.0174532925; +global.RAD2DEG = 57.295779578552306; +global.EPSILON = 0.000001; + +/** +* Tells if one number is power of two (used for textures) +* @method isPowerOfTwo +* @param {v} number +* @return {boolean} +*/ +global.isPowerOfTwo = GL.isPowerOfTwo = function isPowerOfTwo(v) +{ + return ((Math.log(v) / Math.log(2)) % 1) == 0; +} + +/** +* Tells if one number is power of two (used for textures) +* @method isPowerOfTwo +* @param {v} number +* @return {boolean} +*/ +global.nearestPowerOfTwo = GL.nearestPowerOfTwo = function nearestPowerOfTwo(v) +{ + return Math.pow(2, Math.round( Math.log( v ) / Math.log(2) ) ) +} + + +/** +* converts from polar to cartesian +* @method polarToCartesian +* @param {vec3} out +* @param {number} azimuth orientation from 0 to 2PI +* @param {number} inclianation from -PI to PI +* @param {number} radius +* @return {vec3} returns out +*/ +global.polarToCartesian = function( out, azimuth, inclination, radius ) +{ + out = out || vec3.create(); + out[0] = radius * Math.sin(inclination) * Math.cos(azimuth); + out[1] = radius * Math.cos(inclination); + out[2] = radius * Math.sin(inclination) * Math.sin(azimuth); + return out; +} + +/** +* converts from cartesian to polar +* @method cartesianToPolar +* @param {vec3} out +* @param {number} x +* @param {number} y +* @param {number} z +* @return {vec3} returns [azimuth,inclination,radius] +*/ +global.cartesianToPolar = function( out, x,y,z ) +{ + out = out || vec3.create(); + out[2] = Math.sqrt(x*x+y*y+z*z); + out[0] = Math.atan2(x,z); + out[1] = Math.acos(z/out[2]); + return out; +} + +//Global Scope +//better array conversion to string for serializing +var typed_arrays = [ Uint8Array, Int8Array, Uint16Array, Int16Array, Uint32Array, Int32Array, Float32Array, Float64Array ]; +function typedToArray(){ + return Array.prototype.slice.call(this); +} +typed_arrays.forEach( function(v) { + if(!v.prototype.toJSON) + Object.defineProperty( v.prototype, "toJSON", { + value: typedToArray, + enumerable: false + }); +}); + + + +/** +* Get current time in milliseconds +* @method getTime +* @return {number} +*/ +if(typeof(performance) != "undefined") + global.getTime = performance.now.bind(performance); +else + global.getTime = Date.now.bind( Date ); +GL.getTime = global.getTime; + + +global.isFunction = function isFunction(obj) { + return !!(obj && obj.constructor && obj.call && obj.apply); +} + +global.isArray = function isArray(obj) { + return (obj && obj.constructor === Array ); + //var str = Object.prototype.toString.call(obj); + //return str == '[object Array]' || str == '[object Float32Array]'; +} + +global.isNumber = function isNumber(obj) { + return (obj != null && obj.constructor === Number ); +} + +global.getClassName = function getClassName(obj) +{ + if (!obj) + return; + + //from function info, but not standard + if(obj.name) + return obj.name; + + //from sourcecode + if(obj.toString) { + var arr = obj.toString().match( + /function\s*(\w+)/); + if (arr && arr.length == 2) { + return arr[1]; + } + } +} + +/** +* clone one object recursively, only allows objects containing number,strings,typed-arrays or other objects +* @method cloneObject +* @param {Object} object +* @param {Object} target if omited an empty object is created +* @return {Object} +*/ +global.cloneObject = GL.cloneObject = function(o, t) +{ + if(o.constructor !== Object) + throw("cloneObject only can clone pure javascript objects, not classes"); + + t = t || {}; + + for(var i in o) + { + var v = o[i]; + if(v === null) + { + t[i] = null; + continue; + } + + switch(v.constructor) + { + case Int8Array: + case Uint8Array: + case Int16Array: + case Uint16Array: + case Int32Array: + case Uint32Array: + case Float32Array: + case Float64Array: + t[i] = new v.constructor(v); + break; + case Boolean: + case Number: + case String: + t[i] = v; + break; + case Array: + t[i] = v.concat(); //content is not cloned + break; + case Object: + t[i] = GL.cloneObject(v); + break; + } + } + + return t; +} + + +/* SLOW because accepts booleans +function isNumber(obj) { + var str = Object.prototype.toString.call(obj); + return str == '[object Number]' || str == '[object Boolean]'; +} +*/ + +//given a regular expression, a text and a callback, it calls the function every time it finds it +global.regexMap = function regexMap(regex, text, callback) { + var result; + while ((result = regex.exec(text)) != null) { + callback(result); + } +} + +global.createCanvas = GL.createCanvas = function createCanvas(width, height) { + var canvas = document.createElement('canvas'); + canvas.width = width; + canvas.height = height; + return canvas; +} + +global.cloneCanvas = GL.cloneCanvas = function cloneCanvas(c) { + var canvas = document.createElement('canvas'); + canvas.width = c.width; + canvas.height = c.height; + var ctx = canvas.getContext("2d"); + ctx.drawImage(c,0,0); + return canvas; +} + +if(typeof(Image) != "undefined") //not existing inside workers +{ + Image.prototype.getPixels = function() + { + var canvas = document.createElement('canvas'); + canvas.width = this.width; + canvas.height = this.height; + var ctx = canvas.getContext("2d"); + ctx.drawImage(this,0,0); + return ctx.getImageData(0, 0, this.width, this.height).data; + } +} + +//you must pass an object with characters to replace and replace with what {"a":"A","c":"C"} +if(!String.prototype.hasOwnProperty("replaceAll")) + Object.defineProperty(String.prototype, "replaceAll", { + value: function(words){ + var str = this; + for(var i in words) + str = str.split(i).join(words[i]); + return str; + }, + enumerable: false + }); + +/* +String.prototype.replaceAll = function(words){ + var str = this; + for(var i in words) + str = str.split(i).join(words[i]); + return str; +}; +*/ + +//used for hashing keys +if(!String.prototype.hasOwnProperty("hashCode")) + Object.defineProperty(String.prototype, "hashCode", { + value: function(){ + var hash = 0, i, c, l; + if (this.length == 0) return hash; + for (i = 0, l = this.length; i < l; ++i) { + c = this.charCodeAt(i); + hash = ((hash<<5)-hash)+c; + hash |= 0; // Convert to 32bit integer + } + return hash; + }, + enumerable: false + }); + +//avoid errors when Typed array is expected and regular array is found +//Array.prototype.subarray = Array.prototype.slice; +//if(!Array.prototype.hasOwnProperty("subarray")) +// Object.defineProperty(Array.prototype, "subarray", { value: Array.prototype.slice, enumerable: false }); + +if(!Array.prototype.hasOwnProperty("clone")) + Object.defineProperty(Array.prototype, "clone", { value: Array.prototype.concat, enumerable: false }); +if(!Float32Array.prototype.hasOwnProperty("clone")) + Object.defineProperty(Float32Array.prototype, "clone", { value: function() { return new Float32Array(this); }, enumerable: false }); + + +// remove all properties on obj, effectively reverting it to a new object (to reduce garbage) +global.wipeObject = function wipeObject(obj) +{ + for (var p in obj) + { + if (obj.hasOwnProperty(p)) + delete obj[p]; + } +}; + +//copy methods from origin to target +global.extendClass = GL.extendClass = function extendClass( target, origin ) { + for(var i in origin) //copy class properties + { + if(target.hasOwnProperty(i)) + continue; + target[i] = origin[i]; + } + + if(origin.prototype) //copy prototype properties + { + var prop_names = Object.getOwnPropertyNames( origin.prototype ); + for(var i = 0; i < prop_names.length; ++i) //only enumerables + { + var name = prop_names[i]; + //if(!origin.prototype.hasOwnProperty(name)) + // continue; + + if(target.prototype.hasOwnProperty(name)) //avoid overwritting existing ones + continue; + + //copy getters + if(origin.prototype.__lookupGetter__(name)) + target.prototype.__defineGetter__(name, origin.prototype.__lookupGetter__(name)); + else + target.prototype[name] = origin.prototype[name]; + + //and setters + if(origin.prototype.__lookupSetter__(name)) + target.prototype.__defineSetter__(name, origin.prototype.__lookupSetter__(name)); + } + } + + if(!target.hasOwnProperty("superclass")) + Object.defineProperty(target, "superclass", { + get: function() { return origin }, + enumerable: false + }); +} + + + +//simple http request +global.HttpRequest = GL.request = function HttpRequest( url, params, callback, error, options ) +{ + var async = true; + if(options && options.async !== undefined) + async = options.async; + + if(params) + { + var params_str = null; + var params_arr = []; + for(var i in params) + params_arr.push(i + "=" + params[i]); + params_str = params_arr.join("&"); + url = url + "?" + params_str; + } + + var xhr = new XMLHttpRequest(); + xhr.open('GET', url, async); + xhr.onload = function(e) + { + var response = this.response; + var type = this.getResponseHeader("Content-Type"); + if(this.status != 200) + { + LEvent.trigger(xhr,"fail",this.status); + if(error) + error(this.status); + return; + } + + LEvent.trigger(xhr,"done",this.response); + if(callback) + callback(this.response); + return; + } + + xhr.onerror = function(err) + { + LEvent.trigger(xhr,"fail",err); + } + + if(options) + { + for(var i in options) + xhr[i] = options[i]; + if(options.binary) + xhr.responseType = "arraybuffer"; + } + + xhr.send(); + + return xhr; +} + +//cheap simple promises +if( global.XMLHttpRequest ) +{ + if( !XMLHttpRequest.prototype.hasOwnProperty("done") ) + Object.defineProperty( XMLHttpRequest.prototype, "done", { enumerable: false, value: function(callback) + { + LEvent.bind(this,"done", function(e,err) { callback(err); } ); + return this; + }}); + + if( !XMLHttpRequest.prototype.hasOwnProperty("fail") ) + Object.defineProperty( XMLHttpRequest.prototype, "fail", { enumerable: false, value: function(callback) + { + LEvent.bind(this,"fail", function(e,err) { callback(err); } ); + return this; + }}); +} + +global.getFileExtension = function getFileExtension(url) +{ + var question = url.indexOf("?"); + if(question != -1) + url = url.substr(0,question); + var point = url.lastIndexOf("."); + if(point == -1) + return ""; + return url.substr(point+1).toLowerCase(); +} + + +//allows to pack several (text)files inside one single file (useful for shaders) +//every file must start with \filename.ext or /filename.ext +global.loadFileAtlas = GL.loadFileAtlas = function loadFileAtlas(url, callback, sync) +{ + var deferred_callback = null; + + HttpRequest(url, null, function(data) { + var files = GL.processFileAtlas(data); + if(callback) + callback(files); + if(deferred_callback) + deferred_callback(files); + }, alert, sync); + + return { done: function(callback) { deferred_callback = callback; } }; +} + +//This parses a text file that contains several text files (they are separated by "\filename"), and returns an object with every file separatly +global.processFileAtlas = GL.processFileAtlas = function(data, skip_trim) +{ + var lines = data.split("\n"); + var files = {}; + + var current_file_lines = []; + var current_file_name = ""; + for(var i = 0, l = lines.length; i < l; i++) + { + var line = skip_trim ? lines[i] : lines[i].trim(); + if(!line.length) + continue; + if( line[0] != "\\") + { + current_file_lines.push(line); + continue; + } + + if( current_file_lines.length ) + files[ current_file_name ] = current_file_lines.join("\n"); + current_file_lines.length = 0; + current_file_name = line.substr(1); + } + + if( current_file_lines.length ) + files[ current_file_name ] = current_file_lines.join("\n"); + + return files; +} + + +/* +global.halfFloatToFloat = function( h ) +{ + function convertMantissa(i) { + if (i == 0) + return 0 + else if (i < 1024) + { + var m = i << 13; + var e = 0; + while (!(m & 0x00800000)) + { + e -= 0x00800000 + m = m << 1 + } + m &= ~0x00800000 + e += 0x38800000 + return m | e; + } + return 0x38000000 + ((i - 1024) << 13); + } + + function convertExponent(i) { + if (i == 0) + return 0; + else if (i >= 1 && i <= 31) + return i << 23; + else if (i == 31) + return 0x47800000; + else if (i == 32) + return 0x80000000; + else if (i >= 33 && i <= 63) + return 0x80000000 + ((i - 32) << 23); + return 0xC7800000; + } + + function convertOffset(i) { + if (i == 0 || i == 32) + return 0 + return 1024; + } + + var v = convertMantissa( convertOffset( h >> 10) + (h & 0x3ff) ) + convertExponent(h >> 10); + var a = new Uint32Array([v]); + return (new Float32Array(a.buffer))[0]; +} +*/ + +global.typedArrayToArray = function(array) +{ + var r = []; + r.length = array.length; + for(var i = 0; i < array.length; i++) + r[i] = array[i]; + return r; +} + +global.RGBToHex = function(r, g, b) { + r = Math.min(255, r*255)|0; + g = Math.min(255, g*255)|0; + b = Math.min(255, b*255)|0; + return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1); +} + +global.HUEToRGB = function ( p, q, t ){ + if(t < 0) t += 1; + if(t > 1) t -= 1; + if(t < 1/6) return p + (q - p) * 6 * t; + if(t < 1/2) return q; + if(t < 2/3) return p + (q - p) * (2/3 - t) * 6; + return p; +} + +global.HSLToRGB = function( h, s, l, out ){ + var r, g, b; + out = out || vec3.create(); + if(s == 0){ + r = g = b = l; // achromatic + }else{ + var q = l < 0.5 ? l * (1 + s) : l + s - l * s; + var p = 2 * l - q; + r = HUEToRGB(p, q, h + 1/3); + g = HUEToRGB(p, q, h); + b = HUEToRGB(p, q, h - 1/3); + } + out[0] = r; + out[1] = g; + out[2] = b; + return out; +} + +global.hexColorToRGBA = (function() { + //to change the color: from http://www.w3schools.com/cssref/css_colorsfull.asp + var string_colors = { + white: [1,1,1], + black: [0,0,0], + gray: [0.501960813999176, 0.501960813999176, 0.501960813999176], + red: [1,0,0], + orange: [1, 0.6470588445663452, 0], + pink: [1, 0.7529411911964417, 0.7960784435272217], + green: [0, 0.501960813999176, 0], + lime: [0,1,0], + blue: [0,0,1], + violet: [0.9333333373069763, 0.5098039507865906, 0.9333333373069763], + magenta: [1,0,1], + cyan: [0,1,1], + yellow: [1,1,0], + brown: [0.6470588445663452, 0.16470588743686676, 0.16470588743686676], + silver: [0.7529411911964417, 0.7529411911964417, 0.7529411911964417], + gold: [1, 0.843137264251709, 0], + transparent: [0,0,0,0] + }; + + return function( hex, color, alpha ) + { + alpha = (alpha === undefined ? 1 : alpha); + color = color || new Float32Array(4); + color[3] = alpha; + + if(typeof(hex) != "string") + return color; + + + //for those hardcoded colors + var col = string_colors[hex]; + if( col !== undefined ) + { + color.set( col ); + if(color.length == 3) + color[3] = alpha; + else + color[3] *= alpha; + return color; + } + + //rgba colors + var pos = hex.indexOf("rgba("); + if(pos != -1) + { + var str = hex.substr(5,hex.length-2); + str = str.split(","); + color[0] = parseInt( str[0] ) / 255; + color[1] = parseInt( str[1] ) / 255; + color[2] = parseInt( str[2] ) / 255; + color[3] = parseFloat( str[3] ) * alpha; + return color; + } + + var pos = hex.indexOf("hsla("); + if(pos != -1) + { + var str = hex.substr(5,hex.length-2); + str = str.split(","); + HSLToRGB( parseInt( str[0] ) / 360, parseInt( str[1] ) / 100, parseInt( str[2] ) / 100, color ); + color[3] = parseFloat( str[3] ) * alpha; + return color; + } + + color[3] = alpha; + + //rgb colors + var pos = hex.indexOf("rgb("); + if(pos != -1) + { + var str = hex.substr(4,hex.length-2); + str = str.split(","); + color[0] = parseInt( str[0] ) / 255; + color[1] = parseInt( str[1] ) / 255; + color[2] = parseInt( str[2] ) / 255; + return color; + } + + var pos = hex.indexOf("hsl("); + if(pos != -1) + { + var str = hex.substr(4,hex.length-2); + str = str.split(","); + HSLToRGB( parseInt( str[0] ) / 360, parseInt( str[1] ) / 100, parseInt( str[2] ) / 100, color ); + return color; + } + + + //the rest + // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF") + var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; + hex = hex.replace( shorthandRegex, function(m, r, g, b) { + return r + r + g + g + b + b; + }); + + var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); + if(!result) + return color; + + color[0] = parseInt(result[1], 16) / 255; + color[1] = parseInt(result[2], 16) / 255; + color[2] = parseInt(result[3], 16) / 255; + return color; + } +})(); +/** + * @fileoverview dds - Utilities for loading DDS texture files + * @author Brandon Jones + * @version 0.1 + */ + +/* + * Copyright (c) 2012 Brandon Jones + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not + * be misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. + */ + +var DDS = (function () { + + "use strict"; + + // All values and structures referenced from: + // http://msdn.microsoft.com/en-us/library/bb943991.aspx/ + var DDS_MAGIC = 0x20534444; + + var DDSD_CAPS = 0x1, + DDSD_HEIGHT = 0x2, + DDSD_WIDTH = 0x4, + DDSD_PITCH = 0x8, + DDSD_PIXELFORMAT = 0x1000, + DDSD_MIPMAPCOUNT = 0x20000, + DDSD_LINEARSIZE = 0x80000, + DDSD_DEPTH = 0x800000; + + var DDSCAPS_COMPLEX = 0x8, + DDSCAPS_MIPMAP = 0x400000, + DDSCAPS_TEXTURE = 0x1000; + + var DDSCAPS2_CUBEMAP = 0x200, + DDSCAPS2_CUBEMAP_POSITIVEX = 0x400, + DDSCAPS2_CUBEMAP_NEGATIVEX = 0x800, + DDSCAPS2_CUBEMAP_POSITIVEY = 0x1000, + DDSCAPS2_CUBEMAP_NEGATIVEY = 0x2000, + DDSCAPS2_CUBEMAP_POSITIVEZ = 0x4000, + DDSCAPS2_CUBEMAP_NEGATIVEZ = 0x8000, + DDSCAPS2_VOLUME = 0x200000; + + var DDPF_ALPHAPIXELS = 0x1, + DDPF_ALPHA = 0x2, + DDPF_FOURCC = 0x4, + DDPF_RGB = 0x40, + DDPF_YUV = 0x200, + DDPF_LUMINANCE = 0x20000; + + function fourCCToInt32(value) { + return value.charCodeAt(0) + + (value.charCodeAt(1) << 8) + + (value.charCodeAt(2) << 16) + + (value.charCodeAt(3) << 24); + } + + function int32ToFourCC(value) { + return String.fromCharCode( + value & 0xff, + (value >> 8) & 0xff, + (value >> 16) & 0xff, + (value >> 24) & 0xff + ); + } + + var FOURCC_DXT1 = fourCCToInt32("DXT1"); + var FOURCC_DXT3 = fourCCToInt32("DXT3"); + var FOURCC_DXT5 = fourCCToInt32("DXT5"); + + var headerLengthInt = 31; // The header length in 32 bit ints + + // Offsets into the header array + var off_magic = 0; + + var off_size = 1; + var off_flags = 2; + var off_height = 3; + var off_width = 4; + + var off_mipmapCount = 7; + + var off_pfFlags = 20; + var off_pfFourCC = 21; + var off_caps = 27; + + // Little reminder for myself where the above values come from + /*DDS_PIXELFORMAT { + int32 dwSize; // offset: 19 + int32 dwFlags; + char[4] dwFourCC; + int32 dwRGBBitCount; + int32 dwRBitMask; + int32 dwGBitMask; + int32 dwBBitMask; + int32 dwABitMask; // offset: 26 + }; + + DDS_HEADER { + int32 dwSize; // 1 + int32 dwFlags; + int32 dwHeight; + int32 dwWidth; + int32 dwPitchOrLinearSize; + int32 dwDepth; + int32 dwMipMapCount; // offset: 7 + int32[11] dwReserved1; + DDS_PIXELFORMAT ddspf; // offset 19 + int32 dwCaps; // offset: 27 + int32 dwCaps2; + int32 dwCaps3; + int32 dwCaps4; + int32 dwReserved2; // offset 31 + };*/ + + /** + * Transcodes DXT into RGB565. + * Optimizations: + * 1. Use integer math to compute c2 and c3 instead of floating point + * math. Specifically: + * c2 = 5/8 * c0 + 3/8 * c1 + * c3 = 3/8 * c0 + 5/8 * c1 + * This is about a 40% performance improvement. It also appears to + * match what hardware DXT decoders do, as the colors produced + * by this integer math match what hardware produces, while the + * floating point in dxtToRgb565Unoptimized() produce slightly + * different colors (for one GPU this was tested on). + * 2. Unroll the inner loop. Another ~10% improvement. + * 3. Compute r0, g0, b0, r1, g1, b1 only once instead of twice. + * Another 10% improvement. + * 4. Use a Uint16Array instead of a Uint8Array. Another 10% improvement. + * @author Evan Parker + * @param {Uint16Array} src The src DXT bits as a Uint16Array. + * @param {number} srcByteOffset + * @param {number} width + * @param {number} height + * @return {Uint16Array} dst + */ + function dxtToRgb565(src, src16Offset, width, height) { + var c = new Uint16Array(4); + var dst = new Uint16Array(width * height); + var nWords = (width * height) / 4; + var m = 0; + var dstI = 0; + var i = 0; + var r0 = 0, g0 = 0, b0 = 0, r1 = 0, g1 = 0, b1 = 0; + + var blockWidth = width / 4; + var blockHeight = height / 4; + for (var blockY = 0; blockY < blockHeight; blockY++) { + for (var blockX = 0; blockX < blockWidth; blockX++) { + i = src16Offset + 4 * (blockY * blockWidth + blockX); + c[0] = src[i]; + c[1] = src[i + 1]; + r0 = c[0] & 0x1f; + g0 = c[0] & 0x7e0; + b0 = c[0] & 0xf800; + r1 = c[1] & 0x1f; + g1 = c[1] & 0x7e0; + b1 = c[1] & 0xf800; + // Interpolate between c0 and c1 to get c2 and c3. + // Note that we approximate 1/3 as 3/8 and 2/3 as 5/8 for + // speed. This also appears to be what the hardware DXT + // decoder in many GPUs does :) + c[2] = ((5 * r0 + 3 * r1) >> 3) + | (((5 * g0 + 3 * g1) >> 3) & 0x7e0) + | (((5 * b0 + 3 * b1) >> 3) & 0xf800); + c[3] = ((5 * r1 + 3 * r0) >> 3) + | (((5 * g1 + 3 * g0) >> 3) & 0x7e0) + | (((5 * b1 + 3 * b0) >> 3) & 0xf800); + m = src[i + 2]; + dstI = (blockY * 4) * width + blockX * 4; + dst[dstI] = c[m & 0x3]; + dst[dstI + 1] = c[(m >> 2) & 0x3]; + dst[dstI + 2] = c[(m >> 4) & 0x3]; + dst[dstI + 3] = c[(m >> 6) & 0x3]; + dstI += width; + dst[dstI] = c[(m >> 8) & 0x3]; + dst[dstI + 1] = c[(m >> 10) & 0x3]; + dst[dstI + 2] = c[(m >> 12) & 0x3]; + dst[dstI + 3] = c[(m >> 14)]; + m = src[i + 3]; + dstI += width; + dst[dstI] = c[m & 0x3]; + dst[dstI + 1] = c[(m >> 2) & 0x3]; + dst[dstI + 2] = c[(m >> 4) & 0x3]; + dst[dstI + 3] = c[(m >> 6) & 0x3]; + dstI += width; + dst[dstI] = c[(m >> 8) & 0x3]; + dst[dstI + 1] = c[(m >> 10) & 0x3]; + dst[dstI + 2] = c[(m >> 12) & 0x3]; + dst[dstI + 3] = c[(m >> 14)]; + } + } + return dst; + } + + function BGRtoRGB( byteArray ) + { + for(var j = 0, l = byteArray.length, tmp = 0; j < l; j+=4) //BGR fix + { + tmp = byteArray[j]; + byteArray[j] = byteArray[j+2]; + byteArray[j+2] = tmp; + } + } + + function flipDXT( width, blockBytes, byteArray ) + { + //TODO + //var row = Uint8Array(width); + } + + + /** + * Parses a DDS file from the given arrayBuffer and uploads it into the currently bound texture + * + * @param {WebGLRenderingContext} gl WebGL rendering context + * @param {WebGLCompressedTextureS3TC} ext WEBGL_compressed_texture_s3tc extension object + * @param {TypedArray} arrayBuffer Array Buffer containing the DDS files data + * @param {boolean} [loadMipmaps] If false only the top mipmap level will be loaded, otherwise all available mipmaps will be uploaded + * + * @returns {number} Number of mipmaps uploaded, 0 if there was an error + */ + function uploadDDSLevels(gl, ext, arrayBuffer, loadMipmaps) { + var header = new Int32Array(arrayBuffer, 0, headerLengthInt), + fourCC, blockBytes, internalFormat, + width, height, dataLength, dataOffset, is_cubemap, + rgb565Data, byteArray, mipmapCount, i, face; + + if(header[off_magic] != DDS_MAGIC) { + console.error("Invalid magic number in DDS header"); + return 0; + } + + if(!header[off_pfFlags] & DDPF_FOURCC) { + console.error("Unsupported format, must contain a FourCC code"); + return 0; + } + + fourCC = header[off_pfFourCC]; + switch(fourCC) { + case FOURCC_DXT1: + blockBytes = 8; + internalFormat = ext ? ext.COMPRESSED_RGB_S3TC_DXT1_EXT : null; + break; + + /* + case FOURCC_DXT1: + blockBytes = 8; + internalFormat = ext ? ext.COMPRESSED_RGBA_S3TC_DXT1_EXT : null; + break; + */ + + case FOURCC_DXT3: + blockBytes = 16; + internalFormat = ext ? ext.COMPRESSED_RGBA_S3TC_DXT3_EXT : null; + break; + + case FOURCC_DXT5: + blockBytes = 16; + internalFormat = ext ? ext.COMPRESSED_RGBA_S3TC_DXT5_EXT : null; + break; + + default: + blockBytes = 4; + fourCC = null; + internalFormat = gl.RGBA; + //console.error("Unsupported FourCC code:", int32ToFourCC(fourCC), fourCC); + //return null; + } + + mipmapCount = 1; + if(header[off_flags] & DDSD_MIPMAPCOUNT && loadMipmaps !== false) { + mipmapCount = Math.max(1, header[off_mipmapCount]); + } + + width = header[off_width]; + height = header[off_height]; + dataOffset = header[off_size] + 4; + is_cubemap = !!(header[off_caps+1] & DDSCAPS2_CUBEMAP); + + if(is_cubemap) + { + //console.error("Cubemaps not supported in DDS"); + //return null; + + for(face = 0; face < 6; ++face) + { + width = header[off_width]; + height = header[off_height]; + for(var i = 0; i < mipmapCount; ++i) { + if(fourCC) + { + dataLength = Math.max( 4, width )/4 * Math.max( 4, height )/4 * blockBytes; + byteArray = new Uint8Array(arrayBuffer, dataOffset, dataLength); + flipDXT( width, blockBytes, byteArray ); + gl.compressedTexImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + face, i, internalFormat, width, height, 0, byteArray); + } + else + { + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false ); + dataLength = width * height * blockBytes; + byteArray = new Uint8Array(arrayBuffer, dataOffset, dataLength); + BGRtoRGB(byteArray); + gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + face, i, internalFormat, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, byteArray); + } + dataOffset += dataLength; + width *= 0.5; + height *= 0.5; + } + } + } + else //2d texture + { + if(ext) { + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true ); + for(var i = 0; i < mipmapCount; ++i) { + if(fourCC) + { + dataLength = Math.max( 4, width )/4 * Math.max( 4, height )/4 * blockBytes; + byteArray = new Uint8Array(arrayBuffer, dataOffset, dataLength); + gl.compressedTexImage2D(gl.TEXTURE_2D, i, internalFormat, width, height, 0, byteArray); + } + else + { + dataLength = width * height * blockBytes; + byteArray = new Uint8Array(arrayBuffer, dataOffset, dataLength); + BGRtoRGB(byteArray); + gl.texImage2D(gl.TEXTURE_2D, i, internalFormat, width, height, 0, internalFormat, gl.UNSIGNED_BYTE, byteArray); + } + dataOffset += dataLength; + width *= 0.5; + height *= 0.5; + } + } else { + if(fourCC == FOURCC_DXT1) { + dataLength = Math.max( 4, width )/4 * Math.max( 4, height )/4 * blockBytes; + byteArray = new Uint16Array(arrayBuffer); + //Decompress + rgb565Data = dxtToRgb565(byteArray, dataOffset / 2, width, height); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, width, height, 0, gl.RGB, gl.UNSIGNED_SHORT_5_6_5, rgb565Data); + if(loadMipmaps) { + gl.generateMipmap(gl.TEXTURE_2D); + } + } else { + console.error("No manual decoder for", int32ToFourCC(fourCC), "and no native support"); + return 0; + } + } + } + + return mipmapCount; + } + + /** + * Parses a DDS file from the given arrayBuffer and uploads it into the currently bound texture + * + * @param {WebGLRenderingContext} gl WebGL rendering context + * @param {WebGLCompressedTextureS3TC} ext WEBGL_compressed_texture_s3tc extension object + * @param {TypedArray} arrayBuffer Array Buffer containing the DDS files data + * @param {boolean} [loadMipmaps] If false only the top mipmap level will be loaded, otherwise all available mipmaps will be uploaded + * + * @returns {number} Number of mipmaps uploaded, 0 if there was an error + */ + function getDDSLevels( arrayBuffer, compressed_not_supported ) + { + var header = new Int32Array(arrayBuffer, 0, headerLengthInt), + fourCC, blockBytes, internalFormat, + width, height, dataLength, dataOffset, is_cubemap, + rgb565Data, byteArray, mipmapCount, i, face; + + if(header[off_magic] != DDS_MAGIC) { + console.error("Invalid magic number in DDS header"); + return 0; + } + + if(!header[off_pfFlags] & DDPF_FOURCC) { + console.error("Unsupported format, must contain a FourCC code"); + return 0; + } + + fourCC = header[off_pfFourCC]; + switch(fourCC) { + case FOURCC_DXT1: + blockBytes = 8; + internalFormat = "COMPRESSED_RGB_S3TC_DXT1_EXT"; + break; + + case FOURCC_DXT3: + blockBytes = 16; + internalFormat = "COMPRESSED_RGBA_S3TC_DXT3_EXT"; + break; + + case FOURCC_DXT5: + blockBytes = 16; + internalFormat = "COMPRESSED_RGBA_S3TC_DXT5_EXT"; + break; + + default: + blockBytes = 4; + internalFormat = "RGBA"; + //console.error("Unsupported FourCC code:", int32ToFourCC(fourCC), fourCC); + //return null; + } + + mipmapCount = 1; + if(header[off_flags] & DDSD_MIPMAPCOUNT && loadMipmaps !== false) { + mipmapCount = Math.max(1, header[off_mipmapCount]); + } + + width = header[off_width]; + height = header[off_height]; + dataOffset = header[off_size] + 4; + is_cubemap = !!(header[off_caps+1] & DDSCAPS2_CUBEMAP); + + var buffers = []; + + if(is_cubemap) + { + for(var face = 0; face < 6; ++face) + { + width = header[off_width]; + height = header[off_height]; + for(var i = 0; i < mipmapCount; ++i) + { + if(fourCC) + { + dataLength = Math.max( 4, width )/4 * Math.max( 4, height )/4 * blockBytes; + byteArray = new Uint8Array(arrayBuffer, dataOffset, dataLength); + buffers.push({ tex: "TEXTURE_CUBE_MAP", face: face, mipmap: i, internalFormat: internalFormat, width: width, height: height, offset: 0, dataOffset: dataOffset, dataLength: dataLength }); + } + else + { + dataLength = width * height * blockBytes; + byteArray = new Uint8Array(arrayBuffer, dataOffset, dataLength); + BGRtoRGB(byteArray); + buffers.push({ tex: "TEXTURE_CUBE_MAP", face: face, mipmap: i, internalFormat: internalFormat, width: width, height: height, offset: 0, type: "UNSIGNED_BYTE", dataOffset: dataOffset, dataLength: dataLength }); + } + dataOffset += dataLength; + width *= 0.5; + height *= 0.5; + } + } + } + else //2d texture + { + if(!compressed_not_supported) + { + for(var i = 0; i < mipmapCount; ++i) { + dataLength = Math.max( 4, width )/4 * Math.max( 4, height )/4 * blockBytes; + byteArray = new Uint8Array(arrayBuffer, dataOffset, dataLength); + //gl.compressedTexImage2D(gl.TEXTURE_2D, i, internalFormat, width, height, 0, byteArray); + buffers.push({ tex: "TEXTURE_2D", mipmap: i, internalFormat: internalFormat, width: width, height: height, offset: 0, type: "UNSIGNED_BYTE", dataOffset: dataOffset, dataLength: dataLength }); + dataOffset += dataLength; + width *= 0.5; + height *= 0.5; + } + } else { + if(fourCC == FOURCC_DXT1) + { + dataLength = Math.max( 4, width )/4 * Math.max( 4, height )/4 * blockBytes; + byteArray = new Uint16Array(arrayBuffer); + rgb565Data = dxtToRgb565(byteArray, dataOffset / 2, width, height); + //gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, width, height, 0, gl.RGB, gl.UNSIGNED_SHORT_5_6_5, rgb565Data); + buffers.push({ tex: "TEXTURE_2D", mipmap: 0, internalFormat: "RGB", width: width, height: height, offset: 0, format:"RGB", type: "UNSIGNED_SHORT_5_6_5", data: rgb565Data }); + } else { + console.error("No manual decoder for", int32ToFourCC(fourCC), "and no native support"); + return 0; + } + } + } + + return buffers; + } + + /** + * Creates a texture from the DDS file at the given URL. Simple shortcut for the most common use case + * + * @param {WebGLRenderingContext} gl WebGL rendering context + * @param {WebGLCompressedTextureS3TC} ext WEBGL_compressed_texture_s3tc extension object + * @param {string} src URL to DDS file to be loaded + * @param {function} [callback] callback to be fired when the texture has finished loading + * + * @returns {WebGLTexture} New texture that will receive the DDS image data + */ + function loadDDSTextureEx(gl, ext, src, texture, loadMipmaps, callback) { + var xhr = new XMLHttpRequest(); + + xhr.open('GET', src, true); + xhr.responseType = "arraybuffer"; + xhr.onload = function() { + if(this.status == 200) { + var header = new Int32Array(this.response, 0, headerLengthInt) + var is_cubemap = !!(header[off_caps+1] & DDSCAPS2_CUBEMAP); + var tex_type = is_cubemap ? gl.TEXTURE_CUBE_MAP : gl.TEXTURE_2D; + gl.bindTexture(tex_type, texture); + var mipmaps = uploadDDSLevels(gl, ext, this.response, loadMipmaps); + gl.texParameteri(tex_type, gl.TEXTURE_MAG_FILTER, gl.LINEAR); + gl.texParameteri(tex_type, gl.TEXTURE_MIN_FILTER, mipmaps > 1 ? gl.LINEAR_MIPMAP_LINEAR : gl.LINEAR); + gl.bindTexture(tex_type, null); + texture.texture_type = tex_type; + texture.width = header[off_width]; + texture.height = header[off_height]; + } + + if(callback) { + callback(texture); + } + }; + xhr.send(null); + + return texture; + } + + /** + * Creates a texture from the DDS file at the given ArrayBuffer. + * + * @param {WebGLRenderingContext} gl WebGL rendering context + * @param {WebGLCompressedTextureS3TC} ext WEBGL_compressed_texture_s3tc extension object + * @param {ArrayBuffer} data containing the DDS file + * @param {Texture} texture from GL.Texture + * @returns {WebGLTexture} New texture that will receive the DDS image data + */ + function loadDDSTextureFromMemoryEx(gl, ext, data, texture, loadMipmaps) { + var header = new Int32Array(data, 0, headerLengthInt) + var is_cubemap = !!(header[off_caps+1] & DDSCAPS2_CUBEMAP); + var tex_type = is_cubemap ? gl.TEXTURE_CUBE_MAP : gl.TEXTURE_2D; + + var handler = texture.handler || texture; + + gl.bindTexture(tex_type, texture.handler); + + //upload data + var mipmaps = uploadDDSLevels(gl, ext, data, loadMipmaps); + + gl.texParameteri(tex_type, gl.TEXTURE_MAG_FILTER, gl.LINEAR); + gl.texParameteri(tex_type, gl.TEXTURE_MIN_FILTER, mipmaps > 1 ? gl.LINEAR_MIPMAP_LINEAR : gl.LINEAR); + if(is_cubemap) + { + gl.texParameteri(tex_type, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE ); + gl.texParameteri(tex_type, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE ); + } + + gl.bindTexture(tex_type, null); //unbind + if(texture.handler) + { + texture.texture_type = tex_type; + texture.width = header[off_width]; + texture.height = header[off_height]; + } + return texture; + } + + /** + * Extracts the texture info from a DDS file at the given ArrayBuffer. + * + * @param {ArrayBuffer} data containing the DDS file + * + * @returns {Object} contains mipmaps and properties + */ + function getDDSTextureFromMemoryEx(data) { + var header = new Int32Array(data, 0, headerLengthInt) + var is_cubemap = !!(header[off_caps+1] & DDSCAPS2_CUBEMAP); + var tex_type = is_cubemap ? "TEXTURE_CUBE_MAP" : "TEXTURE_2D"; + var buffers = getDDSLevels(data); + + var texture = { + type: tex_type, + buffers: buffers, + data: data, + width: header[off_width], + height: header[off_height] + }; + + return texture; + } + + /** + * Creates a texture from the DDS file at the given URL. Simple shortcut for the most common use case + * + * @param {WebGLRenderingContext} gl WebGL rendering context + * @param {WebGLCompressedTextureS3TC} ext WEBGL_compressed_texture_s3tc extension object + * @param {string} src URL to DDS file to be loaded + * @param {function} [callback] callback to be fired when the texture has finished loading + * + * @returns {WebGLTexture} New texture that will receive the DDS image data + */ + function loadDDSTexture(gl, ext, src, callback) { + var texture = gl.createTexture(); + var ext = gl.getExtension("WEBGL_compressed_texture_s3tc"); + loadDDSTextureEx(gl, ext, src, texture, true, callback); + return texture; + } + + return { + dxtToRgb565: dxtToRgb565, + uploadDDSLevels: uploadDDSLevels, + loadDDSTextureEx: loadDDSTextureEx, + loadDDSTexture: loadDDSTexture, + loadDDSTextureFromMemoryEx: loadDDSTextureFromMemoryEx, + getDDSTextureFromMemoryEx: getDDSTextureFromMemoryEx + }; + +})(); + +if(typeof(global) != "undefined") + global.DDS = DDS; + +/* this file adds some extra functions to gl-matrix library */ +if(typeof(glMatrix) == "undefined") + throw("You must include glMatrix on your project"); + +Math.clamp = function(v,a,b) { return (a > v ? a : (b < v ? b : v)); } + +var V3 = vec3.create; +var M4 = vec3.create; + + +vec3.ZERO = vec3.fromValues(0,0,0); +vec3.FRONT = vec3.fromValues(0,0,-1); +vec3.UP = vec3.fromValues(0,1,0); +vec3.RIGHT = vec3.fromValues(1,0,0); + +vec2.rotate = function(out,vec,angle_in_rad) +{ + var x = vec[0], y = vec[1]; + var cos = Math.cos(angle_in_rad); + var sin = Math.sin(angle_in_rad); + out[0] = x * cos - y * sin; + out[1] = x * sin + y * cos; + return out; +} + +vec3.zero = function(a) +{ + a[0] = a[1] = 0.0; + return a; +} + +//for signed angles +vec2.perpdot = function(a,b) +{ + return a[1] * b[0] + -a[0] * b[1]; +} + +vec2.computeSignedAngle = function( a, b ) +{ + return Math.atan2( vec2.perpdot(a,b), vec2.dot(a,b) ); +} + +vec2.random = function( vec, scale ) +{ + scale = scale || 1.0; + vec[0] = Math.random() * scale; + vec[1] = Math.random() * scale; + return vec; +} + +vec3.zero = function(a) +{ + a[0] = a[1] = a[2] = 0.0; + return a; +} + +vec3.minValue = function(a) +{ + if(a[0] < a[1] && a[0] < a[2]) return a[0]; + if(a[1] < a[2]) return a[1]; + return a[2]; +} + +vec3.maxValue = function(a) +{ + if(a[0] > a[1] && a[0] > a[2]) return a[0]; + if(a[1] > a[2]) return a[1]; + return a[2]; +} + +vec3.minValue = function(a) +{ + if(a[0] < a[1] && a[0] < a[2]) return a[0]; + if(a[1] < a[2]) return a[1]; + return a[2]; +} + +vec3.addValue = function(out,a,v) +{ + out[0] = a[0] + v; + out[1] = a[1] + v; + out[2] = a[2] + v; +} + +vec3.subValue = function(out,a,v) +{ + out[0] = a[0] - v; + out[1] = a[1] - v; + out[2] = a[2] - v; +} + +vec3.toArray = function(vec) +{ + return [vec[0],vec[1],vec[2]]; +} + +vec3.rotateX = function(out,vec,angle_in_rad) +{ + var y = vec[1], z = vec[2]; + var cos = Math.cos(angle_in_rad); + var sin = Math.sin(angle_in_rad); + + out[0] = vec[0]; + out[1] = y * cos - z * sin; + out[2] = y * sin + z * cos; + return out; +} + +vec3.rotateY = function(out,vec,angle_in_rad) +{ + var x = vec[0], z = vec[2]; + var cos = Math.cos(angle_in_rad); + var sin = Math.sin(angle_in_rad); + + out[0] = x * cos - z * sin; + out[1] = vec[1]; + out[2] = x * sin + z * cos; + return out; +} + +vec3.rotateZ = function(out,vec,angle_in_rad) +{ + var x = vec[0], y = vec[1]; + var cos = Math.cos(angle_in_rad); + var sin = Math.sin(angle_in_rad); + + out[0] = x * cos - y * sin; + out[1] = x * sin + y * cos; + out[2] = vec[2]; + return out; +} + +vec3.angle = function( a, b ) +{ + return Math.acos( vec3.dot(a,b) ); +} + +vec3.signedAngle = function(from, to, axis) +{ + var unsignedAngle = vec3.angle( from, to ); + var cross_x = from[1] * to[2] - from[2] * to[1]; + var cross_y = from[2] * to[0] - from[0] * to[2]; + var cross_z = from[0] * to[1] - from[1] * to[0]; + var sign = Math.sign(axis[0] * cross_x + axis[1] * cross_y + axis[2] * cross_z); + return unsignedAngle * sign; +} + +vec3.random = function(vec, scale) +{ + scale = scale || 1.0; + vec[0] = Math.random() * scale; + vec[1] = Math.random() * scale; + vec[2] = Math.random() * scale; + return vec; +} + +//converts a polar coordinate (radius, lat, long) to (x,y,z) +vec3.polarToCartesian = function(out, v) +{ + var r = v[0]; + var lat = v[1]; + var lon = v[2]; + out[0] = r * Math.cos(lat) * Math.sin(lon); + out[1] = r * Math.sin(lat); + out[2] = r * Math.cos(lat) * Math.cos(lon); + return out; +} + +vec3.reflect = function(out, v, n) +{ + var x = v[0]; var y = v[1]; var z = v[2]; + vec3.scale( out, n, -2 * vec3.dot(v,n) ); + out[0] += x; + out[1] += y; + out[2] += z; + return out; +} + +/* VEC4 */ +vec4.random = function(vec, scale) +{ + scale = scale || 1.0; + vec[0] = Math.random() * scale; + vec[1] = Math.random() * scale; + vec[2] = Math.random() * scale; + vec[3] = Math.random() * scale; + return vec; +} + +vec4.toArray = function(vec) +{ + return [vec[0],vec[1],vec[2],vec[3]]; +} + + +/** MATRIX ********************/ +mat3.IDENTITY = mat3.create(); +mat4.IDENTITY = mat4.create(); + +mat4.toArray = function(mat) +{ + return [mat[0],mat[1],mat[2],mat[3],mat[4],mat[5],mat[6],mat[7],mat[8],mat[9],mat[10],mat[11],mat[12],mat[13],mat[14],mat[15]]; +} + +mat4.setUpAndOrthonormalize = function(out, m, up) +{ + if(m != out) + mat4.copy(out,m); + var right = out.subarray(0,3); + vec3.normalize(out.subarray(4,7),up); + var front = out.subarray(8,11); + vec3.cross( right, up, front ); + vec3.normalize( right, right ); + vec3.cross( front, right, up ); + vec3.normalize( front, front ); +} + +mat4.multiplyVec3 = function(out, m, a) { + var x = a[0], y = a[1], z = a[2]; + out[0] = m[0] * x + m[4] * y + m[8] * z + m[12]; + out[1] = m[1] * x + m[5] * y + m[9] * z + m[13]; + out[2] = m[2] * x + m[6] * y + m[10] * z + m[14]; + return out; +}; + +//from https://github.com/hughsk/from-3d-to-2d/blob/master/index.js +//m should be a projection matrix (or a VP or MVP) +//projects vector from 3D to 2D and returns the value in normalized screen space +mat4.projectVec3 = function(out, m, a) +{ + var ix = a[0]; + var iy = a[1]; + var iz = a[2]; + + var ox = m[0] * ix + m[4] * iy + m[8] * iz + m[12]; + var oy = m[1] * ix + m[5] * iy + m[9] * iz + m[13]; + var oz = m[2] * ix + m[6] * iy + m[10] * iz + m[14]; + var ow = m[3] * ix + m[7] * iy + m[11] * iz + m[15]; + + out[0] = (ox / ow + 1) / 2; + out[1] = (oy / ow + 1) / 2; + out[2] = (oz / ow + 1) / 2; + return out; +}; + + +//from https://github.com/hughsk/from-3d-to-2d/blob/master/index.js +vec3.project = function(out, vec, mvp, viewport) { + viewport = viewport || gl.viewport_data; + + var m = mvp; + + var ix = vec[0]; + var iy = vec[1]; + var iz = vec[2]; + + var ox = m[0] * ix + m[4] * iy + m[8] * iz + m[12]; + var oy = m[1] * ix + m[5] * iy + m[9] * iz + m[13]; + var oz = m[2] * ix + m[6] * iy + m[10] * iz + m[14]; + var ow = m[3] * ix + m[7] * iy + m[11] * iz + m[15]; + + var projx = (ox / ow + 1) / 2; + var projy = 1 - (oy / ow + 1) / 2; + var projz = (oz / ow + 1) / 2; + + out[0] = projx * viewport[2] + viewport[0]; + out[1] = projy * viewport[3] + viewport[1]; + out[2] = projz; //ow + return out; +}; + +var unprojectMat = mat4.create(); +var unprojectVec = vec4.create(); + +vec3.unproject = function (out, vec, viewprojection, viewport) { + + var m = unprojectMat; + var v = unprojectVec; + + v[0] = (vec[0] - viewport[0]) * 2.0 / viewport[2] - 1.0; + v[1] = (vec[1] - viewport[1]) * 2.0 / viewport[3] - 1.0; + v[2] = 2.0 * vec[2] - 1.0; + v[3] = 1.0; + + if(!mat4.invert(m,viewprojection)) + return null; + + vec4.transformMat4(v, v, m); + if(v[3] === 0.0) + return null; + + out[0] = v[0] / v[3]; + out[1] = v[1] / v[3]; + out[2] = v[2] / v[3]; + + return out; +}; + +//without translation +mat4.rotateVec3 = function(out, m, a) { + var x = a[0], y = a[1], z = a[2]; + out[0] = m[0] * x + m[4] * y + m[8] * z; + out[1] = m[1] * x + m[5] * y + m[9] * z; + out[2] = m[2] * x + m[6] * y + m[10] * z; + return out; +}; + +mat4.fromTranslationFrontTop = function (out, pos, front, top) +{ + vec3.cross(out.subarray(0,3), front, top); + out.set(top,4); + out.set(front,8); + out.set(pos,12); + return out; +} + + +mat4.translationMatrix = function (v) +{ + var out = mat4.create(); + out[12] = v[0]; + out[13] = v[1]; + out[14] = v[2]; + return out; +} + +mat4.setTranslation = function (out, v) +{ + out[12] = v[0]; + out[13] = v[1]; + out[14] = v[2]; + return out; +} + + +mat4.getTranslation = function (out, matrix) +{ + out[0] = matrix[12]; + out[1] = matrix[13]; + out[2] = matrix[14]; + return out; +} + +//returns the matrix without rotation +mat4.toRotationMat4 = function (out, mat) { + mat4.copy(out,mat); + out[12] = out[13] = out[14] = 0.0; + return out; +}; + +mat4.swapRows = function(out, mat, row, row2) +{ + if(out != mat) + { + mat4.copy(out, mat); + out[4*row] = mat[4*row2]; + out[4*row+1] = mat[4*row2+1]; + out[4*row+2] = mat[4*row2+2]; + out[4*row+3] = mat[4*row2+3]; + out[4*row2] = mat[4*row]; + out[4*row2+1] = mat[4*row+1]; + out[4*row2+2] = mat[4*row+2]; + out[4*row2+3] = mat[4*row+3]; + return out; + } + + var temp = new Float32Array(matrix.subarray(row*4,row*5)); + matrix.set( matrix.subarray(row2*4,row2*5), row*4 ); + matrix.set( temp, row2*4 ); + return out; +} + +//used in skinning +mat4.scaleAndAdd = function(out, mat, mat2, v) +{ + out[0] = mat[0] + mat2[0] * v; out[1] = mat[1] + mat2[1] * v; out[2] = mat[2] + mat2[2] * v; out[3] = mat[3] + mat2[3] * v; + out[4] = mat[4] + mat2[4] * v; out[5] = mat[5] + mat2[5] * v; out[6] = mat[6] + mat2[6] * v; out[7] = mat[7] + mat2[7] * v; + out[8] = mat[8] + mat2[8] * v; out[9] = mat[9] + mat2[9] * v; out[10] = mat[10] + mat2[10] * v; out[11] = mat[11] + mat2[11] * v; + out[12] = mat[12] + mat2[12] * v; out[13] = mat[13] + mat2[13] * v; out[14] = mat[14] + mat2[14] * v; out[15] = mat[15] + mat2[15] * v; + return out; +} + +quat.fromAxisAngle = function(axis, rad) +{ + var out = quat.create(); + rad = rad * 0.5; + var s = Math.sin(rad); + out[0] = s * axis[0]; + out[1] = s * axis[1]; + out[2] = s * axis[2]; + out[3] = Math.cos(rad); + return out; +} + +//from https://answers.unity.com/questions/467614/what-is-the-source-code-of-quaternionlookrotation.html +quat.lookRotation = (function(){ + var vector = vec3.create(); + var vector2 = vec3.create(); + var vector3 = vec3.create(); + + return function( q, front, up ) + { + vec3.normalize(vector,front); + vec3.cross( vector2, up, vector ); + vec3.normalize(vector2,vector2); + vec3.cross( vector3, vector, vector2 ); + + var m00 = vector2[0]; + var m01 = vector2[1]; + var m02 = vector2[2]; + var m10 = vector3[0]; + var m11 = vector3[1]; + var m12 = vector3[2]; + var m20 = vector[0]; + var m21 = vector[1]; + var m22 = vector[2]; + + var num8 = (m00 + m11) + m22; + + if (num8 > 0) + { + var num = Math.sqrt(num8 + 1); + q[3] = num * 0.5; + num = 0.5 / num; + q[0] = (m12 - m21) * num; + q[1] = (m20 - m02) * num; + q[2] = (m01 - m10) * num; + return q; + } + if ((m00 >= m11) && (m00 >= m22)) + { + var num7 = Math.sqrt(((1 + m00) - m11) - m22); + var num4 = 0.5 / num7; + q[0] = 0.5 * num7; + q[1] = (m01 + m10) * num4; + q[2] = (m02 + m20) * num4; + q[3] = (m12 - m21) * num4; + return q; + } + if (m11 > m22) + { + var num6 = Math.sqrt(((1 + m11) - m00) - m22); + var num3 = 0.5 / num6; + q[0] = (m10+ m01) * num3; + q[1] = 0.5 * num6; + q[2] = (m21 + m12) * num3; + q[3] = (m20 - m02) * num3; + return q; + } + var num5 = Math.sqrt(((1 + m22) - m00) - m11); + var num2 = 0.5 / num5; + q[0] = (m20 + m02) * num2; + q[1] = (m21 + m12) * num2; + q[2] = 0.5 * num5; + q[3] = (m01 - m10) * num2; + return q; + }; +})(); + +/* +quat.toEuler = function(out, quat) { + var q = quat; + var heading, attitude, bank; + + if( (q[0]*q[1] + q[2]*q[3]) == 0.5 ) + { + heading = 2 * Math.atan2(q[0],q[3]); + bank = 0; + attitude = 0; //¿? + } + else if( (q[0]*q[1] + q[2]*q[3]) == 0.5 ) + { + heading = -2 * Math.atan2(q[0],q[3]); + bank = 0; + attitude = 0; //¿? + } + else + { + heading = Math.atan2( 2*(q[1]*q[3] - q[0]*q[2]) , 1 - 2 * (q[1]*q[1] - q[2]*q[2]) ); + attitude = Math.asin( 2*(q[0]*q[1] - q[2]*q[3]) ); + bank = Math.atan2( 2*(q[0]*q[3] - q[1]*q[2]), 1 - 2*(q[0]*q[0] - q[2]*q[2]) ); + } + + if(!out) + out = vec3.create(); + vec3.set(out, heading, attitude, bank); + return out; +} +*/ + +/* +//FROM https://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles +//doesnt work well +quat.toEuler = function(out, q) +{ + var yaw = Math.atan2(2*q[0]*q[3] + 2*q[1]*q[2], 1 - 2*q[2]*q[2] - 2*q[3]*q[3]); + var pitch = Math.asin(2*q[0]*q[2] - 2*q[3]*q[1]); + var roll = Math.atan2(2*q[0]*q[1] + 2*q[2]*q[3], 1 - 2*q[1]*q[1] - 2*q[2]*q[2]); + if(!out) + out = vec3.create(); + vec3.set(out, yaw, pitch, roll); + return out; +} + +quat.fromEuler = function(out, vec) { + var yaw = vec[0]; + var pitch = vec[1]; + var roll = vec[2]; + + var C1 = Math.cos(yaw*0.5); + var C2 = Math.cos(pitch*0.5); + var C3 = Math.cos(roll*0.5); + var S1 = Math.sin(yaw*0.5); + var S2 = Math.sin(pitch*0.5); + var S3 = Math.sin(roll*0.5); + + var x = C1*C2*C3 + S1*S2*S3; + var y = S1*C2*C3 - C1*S2*S3; + var z = C1*S2*C3 + S1*C2*S3; + var w = C1*C2*S3 - S1*S2*C3; + + quat.set(out, x,y,z,w ); + quat.normalize(out,out); //necessary? + return out; +} +*/ + +quat.toEuler = function(out, q) +{ + var heading = Math.atan2(2*q[1]*q[3] - 2*q[0]*q[2], 1 - 2*q[1]*q[1] - 2*q[2]*q[2]); + var attitude = Math.asin(2*q[0]*q[1] + 2*q[2]*q[3]); + var bank = Math.atan2(2*q[0]*q[3] - 2*q[1]*q[2], 1 - 2*q[0]*q[0] - 2*q[2]*q[2]); + if(!out) + out = vec3.create(); + vec3.set(out, heading, attitude, bank); + return out; +} + +quat.fromEuler = function(out, vec) { + var heading = vec[0]; + var attitude = vec[1]; + var bank = vec[2]; + + var C1 = Math.cos(heading); //yaw + var C2 = Math.cos(attitude); //pitch + var C3 = Math.cos(bank); //roll + var S1 = Math.sin(heading); + var S2 = Math.sin(attitude); + var S3 = Math.sin(bank); + + var w = Math.sqrt(1.0 + C1 * C2 + C1*C3 - S1 * S2 * S3 + C2*C3) * 0.5; + if(w == 0.0) + { + w = 0.000001; + //quat.set(out, 0,0,0,1 ); + //return out; + } + + var x = (C2 * S3 + C1 * S3 + S1 * S2 * C3) / (4.0 * w); + var y = (S1 * C2 + S1 * C3 + C1 * S2 * S3) / (4.0 * w); + var z = (-S1 * S3 + C1 * S2 * C3 + S2) /(4.0 * w); + quat.set(out, x,y,z,w ); + quat.normalize(out,out); + return out; +}; + + +//not tested +quat.fromMat4 = function(out,m) +{ + var trace = m[0] + m[5] + m[10]; + if ( trace > 0.0 ) + { + var s = Math.sqrt( trace + 1.0 ); + out[3] = s * 0.5;//w + var recip = 0.5 / s; + out[0] = ( m[9] - m[6] ) * recip; //2,1 1,2 + out[1] = ( m[2] - m[8] ) * recip; //0,2 2,0 + out[2] = ( m[4] - m[1] ) * recip; //1,0 0,1 + } + else + { + var i = 0; + if( m[5] > m[0] ) + i = 1; + if( m[10] > m[i*4+i] ) + i = 2; + var j = ( i + 1 ) % 3; + var k = ( j + 1 ) % 3; + var s = Math.sqrt( m[i*4+i] - m[j*4+j] - m[k*4+k] + 1.0 ); + out[i] = 0.5 * s; + var recip = 0.5 / s; + out[3] = ( m[k*4+j] - m[j*4+k] ) * recip;//w + out[j] = ( m[j*4+i] + m[i*4+j] ) * recip; + out[k] = ( m[k*4+i] + m[i*4+k] ) * recip; + } + quat.normalize(out,out); +} + +//col according to common matrix notation, here are stored as rows +vec3.getMat3Column = function(out, m, index ) +{ + out[0] = m[index*3]; + out[1] = m[index*3 + 1]; + out[2] = m[index*3 + 2]; + return out; +} + +mat3.setColumn = function(out, v, index ) +{ + out[index*3] = v[0]; + out[index*3+1] = v[1]; + out[index*3+2] = v[2]; + return out; +} + + +//http://matthias-mueller-fischer.ch/publications/stablePolarDecomp.pdf +//reusing the previous quaternion as an indicator to keep perpendicularity +quat.fromMat3AndQuat = (function(){ + var temp_mat3 = mat3.create(); + var temp_quat = quat.create(); + var Rcol0 = vec3.create(); + var Rcol1 = vec3.create(); + var Rcol2 = vec3.create(); + var Acol0 = vec3.create(); + var Acol1 = vec3.create(); + var Acol2 = vec3.create(); + var RAcross0 = vec3.create(); + var RAcross1 = vec3.create(); + var RAcross2 = vec3.create(); + var omega = vec3.create(); + var axis = mat3.create(); + + return function( q, A, max_iter ) + { + max_iter = max_iter || 25; + for (var iter = 0; iter < max_iter; ++iter) + { + var R = mat3.fromQuat( temp_mat3, q ); + vec3.getMat3Column(Rcol0,R,0); + vec3.getMat3Column(Rcol1,R,1); + vec3.getMat3Column(Rcol2,R,2); + vec3.getMat3Column(Acol0,A,0); + vec3.getMat3Column(Acol1,A,1); + vec3.getMat3Column(Acol2,A,2); + vec3.cross( RAcross0, Rcol0, Acol0 ); + vec3.cross( RAcross1, Rcol1, Acol1 ); + vec3.cross( RAcross2, Rcol2, Acol2 ); + vec3.add( omega, RAcross0, RAcross1 ); + vec3.add( omega, omega, RAcross2 ); + var d = 1.0 / Math.abs( vec3.dot(Rcol0,Acol0) + vec3.dot(Rcol1,Acol1) + vec3.dot(Rcol2,Acol2) ) + 1.0e-9; + vec3.scale( omega, omega, d ); + var w = vec3.length(omega); + if (w < 1.0e-9) + break; + vec3.scale(omega,omega,1/w); //normalize + quat.setAxisAngle( temp_quat, omega, w ); + quat.mul( q, temp_quat, q ); + quat.normalize(q,q); + } + return q; + }; +})(); + +//http://number-none.com/product/IK%20with%20Quaternion%20Joint%20Limits/ +quat.rotateToFrom = (function(){ + var tmp = vec3.create(); + return function(out, v1, v2) + { + out = out || quat.create(); + var axis = vec3.cross(tmp, v1, v2); + var dot = vec3.dot(v1, v2); + if( dot < -1 + 0.01){ + out[0] = 0; + out[1] = 1; + out[2] = 0; + out[3] = 0; + return out; + } + out[0] = axis[0] * 0.5; + out[1] = axis[1] * 0.5; + out[2] = axis[2] * 0.5; + out[3] = (1 + dot) * 0.5; + quat.normalize(out, out); + return out; + } +})(); + +quat.lookAt = (function(){ + var axis = vec3.create(); + + return function( out, forwardVector, up ) + { + var dot = vec3.dot( vec3.FRONT, forwardVector ); + + if ( Math.abs( dot - (-1.0)) < 0.000001 ) + { + out.set( vec3.UP ); + out[3] = Math.PI; + return out; + } + if ( Math.abs(dot - 1.0) < 0.000001 ) + { + return quat.identity( out ); + } + + var rotAngle = Math.acos( dot ); + vec3.cross( axis, vec3.FRONT, forwardVector ); + vec3.normalize( axis, axis ); + quat.setAxisAngle( out, axis, rotAngle ); + return out; + } +})(); + + + + +/** +* @namespace GL +*/ + +/** +* Indexer used to reuse vertices among a mesh +* @class Indexer +* @constructor +*/ +GL.Indexer = function Indexer() { + this.unique = []; + this.indices = []; + this.map = {}; +} +GL.Indexer.prototype = { + add: function(obj) { + var key = JSON.stringify(obj); + if (!(key in this.map)) { + this.map[key] = this.unique.length; + this.unique.push(obj); + } + return this.map[key]; + } +}; + +/** +* A data buffer to be stored in the GPU +* @class Buffer +* @constructor +* @param {Number} target gl.ARRAY_BUFFER, ELEMENT_ARRAY_BUFFER +* @param {ArrayBufferView} data the data in typed-array format +* @param {number} spacing number of numbers per component (3 per vertex, 2 per uvs...), default 3 +* @param {enum} stream_type default gl.STATIC_DRAW (other: gl.DYNAMIC_DRAW, gl.STREAM_DRAW +*/ +GL.Buffer = function Buffer( target, data, spacing, stream_type, gl ) { + if(GL.debug) + console.log("GL.Buffer created"); + + if(gl !== null) + gl = gl || global.gl; + this.gl = gl; + + this.buffer = null; //webgl buffer + this.target = target; //GL.ARRAY_BUFFER, GL.ELEMENT_ARRAY_BUFFER + this.attribute = null; //name of the attribute in the shader ("a_vertex","a_normal","a_coord",...) + + //optional + this.data = data; + this.spacing = spacing || 3; + + if(this.data && this.gl) + this.upload(stream_type); +} + +/** +* binds the buffer to a attrib location +* @method bind +* @param {number} location the location of the shader (from shader.attributes[ name ]) +*/ +GL.Buffer.prototype.bind = function( location, gl ) +{ + gl = gl || this.gl; + + gl.bindBuffer( gl.ARRAY_BUFFER, this.buffer ); + gl.enableVertexAttribArray( location ); + gl.vertexAttribPointer( location, this.spacing, this.buffer.gl_type, false, 0, 0); +} + +/** +* unbinds the buffer from an attrib location +* @method unbind +* @param {number} location the location of the shader +*/ +GL.Buffer.prototype.unbind = function( location, gl ) +{ + gl = gl || this.gl; + gl.disableVertexAttribArray( location ); +} + +/** +* Applies an action to every vertex in this buffer +* @method forEach +* @param {function} callback to be called for every vertex (or whatever is contained in the buffer) +*/ +GL.Buffer.prototype.forEach = function(callback) +{ + var d = this.data; + for (var i = 0, s = this.spacing, l = d.length; i < l; i += s) + { + callback(d.subarray(i,i+s),i); + } + return this; //to concatenate +} + +/** +* Applies a mat4 transform to every triplets in the buffer (assuming they are points) +* No upload is performed (to ensure efficiency in case there are several operations performed) +* @method applyTransform +* @param {mat4} mat +*/ +GL.Buffer.prototype.applyTransform = function(mat) +{ + var d = this.data; + for (var i = 0, s = this.spacing, l = d.length; i < l; i += s) + { + var v = d.subarray(i,i+s); + vec3.transformMat4(v,v,mat); + } + return this; //to concatenate +} + +/** +* Uploads the buffer data (stored in this.data) to the GPU +* @method upload +* @param {number} stream_type default gl.STATIC_DRAW (other: gl.DYNAMIC_DRAW, gl.STREAM_DRAW +*/ +GL.Buffer.prototype.upload = function( stream_type ) { //default gl.STATIC_DRAW (other: gl.DYNAMIC_DRAW, gl.STREAM_DRAW ) + var spacing = this.spacing || 3; //default spacing + var gl = this.gl; + if(!gl) + return; + + if(!this.data) + throw("No data supplied"); + + var data = this.data; + if(!data.buffer) + throw("Buffers must be typed arrays"); + + //I store some stuff inside the WebGL buffer instance, it is supported + this.buffer = this.buffer || gl.createBuffer(); + if(!this.buffer) + return; //if the context is lost... + + this.buffer.length = data.length; + this.buffer.spacing = spacing; + + //store the data format + switch( data.constructor ) + { + case Int8Array: this.buffer.gl_type = gl.BYTE; break; + case Uint8ClampedArray: + case Uint8Array: this.buffer.gl_type = gl.UNSIGNED_BYTE; break; + case Int16Array: this.buffer.gl_type = gl.SHORT; break; + case Uint16Array: this.buffer.gl_type = gl.UNSIGNED_SHORT; break; + case Int32Array: this.buffer.gl_type = gl.INT; break; + case Uint32Array: this.buffer.gl_type = gl.UNSIGNED_INT; break; + case Float32Array: this.buffer.gl_type = gl.FLOAT; break; + default: throw("unsupported buffer type"); + } + + if(this.target == gl.ARRAY_BUFFER && ( this.buffer.gl_type == gl.INT || this.buffer.gl_type == gl.UNSIGNED_INT )) + { + console.warn("WebGL does not support UINT32 or INT32 as vertex buffer types, converting to FLOAT"); + this.buffer.gl_type = gl.FLOAT; + data = new Float32Array(data); + } + + gl.bindBuffer(this.target, this.buffer); + gl.bufferData(this.target, data , stream_type || this.stream_type || gl.STATIC_DRAW); +}; +//legacy +GL.Buffer.prototype.compile = GL.Buffer.prototype.upload; + + +/** +* Assign data to buffer and uploads it (it allows range) +* @method setData +* @param {ArrayBufferView} data in Float32Array format usually +* @param {number} offset offset in bytes +*/ +GL.Buffer.prototype.setData = function( data, offset ) +{ + if(!data.buffer) + throw("Data must be typed array"); + offset = offset || 0; + + if(!this.data) + { + this.data = data; + this.upload(); + return; + } + else if( this.data.length < data.length ) + throw("buffer is not big enough, you cannot set data to a smaller buffer"); + + if(this.data != data) + { + if(this.data.length == data.length) + { + this.data.set( data ); + this.upload(); + return; + } + + //upload just part of it + var new_data_view = new Uint8Array( data.buffer, data.buffer.byteOffset, data.buffer.byteLength ); + var data_view = new Uint8Array( this.data.buffer ); + data_view.set( new_data_view, offset ); + this.uploadRange( offset, new_data_view.length ); + } + +}; + + +/** +* Uploads part of the buffer data (stored in this.data) to the GPU +* @method uploadRange +* @param {number} start offset in bytes +* @param {number} size sizes in bytes +*/ +GL.Buffer.prototype.uploadRange = function(start, size) +{ + if(!this.data) + throw("No data stored in this buffer"); + + var data = this.data; + if(!data.buffer) + throw("Buffers must be typed arrays"); + + //cut fragment to upload (no way to avoid GC here, no function to specify the size in WebGL 1.0, but there is one in WebGL 2.0) + var view = new Uint8Array( this.data.buffer, start, size ); + + var gl = this.gl; + gl.bindBuffer(this.target, this.buffer); + gl.bufferSubData(this.target, start, view ); +}; + +/** +* Clones one buffer (it allows to share the same data between both buffers) +* @method clone +* @param {boolean} share if you want that both buffers share the same data (default false) +* return {GL.Buffer} buffer cloned +*/ +GL.Buffer.prototype.clone = function(share) +{ + var buffer = new GL.Buffer(); + if(share) + { + for(var i in this) + buffer[i] = this[i]; + } + else + { + if(this.target) + buffer.target = this.target; + if(this.gl) + buffer.gl = this.gl; + if(this.spacing) + buffer.spacing = this.spacing; + if(this.data) //clone data + { + buffer.data = new global[ this.data.constructor ]( this.data ); + buffer.upload(); + } + } + return buffer; +} + + +GL.Buffer.prototype.toJSON = function() +{ + if(!this.data) + { + console.error("cannot serialize a mesh without data"); + return null; + } + + return { + data_type: getClassName(this.data), + data: this.data.toJSON(), + target: this.target, + attribute: this.attribute, + spacing: this.spacing + }; +} + +GL.Buffer.prototype.fromJSON = function(o) +{ + var data_type = global[ o.data_type ] || Float32Array; + this.data = new data_type( o.data ); //cloned + this.target = o.target; + this.spacing = o.spacing || 3; + this.attribute = o.attribute; + this.upload( GL.STATIC_DRAW ); +} + +/** +* Deletes the content from the GPU and destroys the handler +* @method delete +*/ +GL.Buffer.prototype.delete = function() +{ + var gl = this.gl; + gl.deleteBuffer( this.buffer ); + this.buffer = null; +} + +/** +* Base class for meshes, it wraps several buffers and some global info like the bounding box +* @class Mesh +* @param {Object} vertexBuffers object with all the vertex streams +* @param {Object} indexBuffers object with all the indices streams +* @param {Object} options +* @param {WebGLContext} gl [Optional] gl context where to create the mesh +* @constructor +*/ +global.Mesh = GL.Mesh = function Mesh( vertexbuffers, indexbuffers, options, gl ) +{ + if(GL.debug) + console.log("GL.Mesh created"); + + if( gl !== null ) + { + gl = gl || global.gl; + this.gl = gl; + } + + //used to avoid problems with resources moving between different webgl context + this._context_id = gl.context_id; + + this.vertexBuffers = {}; + this.indexBuffers = {}; + + //here you can store extra info, like groups, which is an array of { name, start, length, material } + this.info = { + groups: [] + }; + this._bounding = BBox.create(); //here you can store a AABB in BBox format + + if(vertexbuffers || indexbuffers) + this.addBuffers( vertexbuffers, indexbuffers, options ? options.stream_type : null ); + + if(options) + for(var i in options) + this[i] = options[i]; +}; + +Mesh.common_buffers = { + "vertices": { spacing:3, attribute: "a_vertex"}, + "vertices2D": { spacing:2, attribute: "a_vertex2D"}, + "normals": { spacing:3, attribute: "a_normal"}, + "coords": { spacing:2, attribute: "a_coord"}, + "coords1": { spacing:2, attribute: "a_coord1"}, + "coords2": { spacing:2, attribute: "a_coord2"}, + "colors": { spacing:4, attribute: "a_color"}, + "tangents": { spacing:3, attribute: "a_tangent"}, + "bone_indices": { spacing:4, attribute: "a_bone_indices", type: Uint8Array }, + "weights": { spacing:4, attribute: "a_weights"}, + "extra": { spacing:1, attribute: "a_extra"}, + "extra2": { spacing:2, attribute: "a_extra2"}, + "extra3": { spacing:3, attribute: "a_extra3"}, + "extra4": { spacing:4, attribute: "a_extra4"} +}; + +Mesh.default_datatype = Float32Array; + +Object.defineProperty( Mesh.prototype, "bounding", { + set: function(v) + { + if(!v) + return; + if(v.length < 13) + throw("Bounding must use the BBox bounding format of 13 floats: center, halfsize, min, max, radius"); + this._bounding.set(v); + }, + get: function() + { + return this._bounding; + } +}); + +/** +* Adds buffer to mesh +* @method addBuffer +* @param {string} name +* @param {Buffer} buffer +*/ + +Mesh.prototype.addBuffer = function(name, buffer) +{ + if(buffer.target == gl.ARRAY_BUFFER) + this.vertexBuffers[name] = buffer; + else + this.indexBuffers[name] = buffer; + + if(!buffer.attribute) + { + var info = GL.Mesh.common_buffers[name]; + if(info) + buffer.attribute = info.attribute; + } +} + + +/** +* Adds vertex and indices buffers to a mesh +* @method addBuffers +* @param {Object} vertexBuffers object with all the vertex streams +* @param {Object} indexBuffers object with all the indices streams +* @param {enum} stream_type default gl.STATIC_DRAW (other: gl.DYNAMIC_DRAW, gl.STREAM_DRAW ) +*/ +Mesh.prototype.addBuffers = function( vertexbuffers, indexbuffers, stream_type ) +{ + var num_vertices = 0; + + if(this.vertexBuffers["vertices"]) + num_vertices = this.vertexBuffers["vertices"].data.length / 3; + + for(var i in vertexbuffers) + { + var data = vertexbuffers[i]; + if(!data) + continue; + + if( data.constructor == GL.Buffer ) + { + data = data.data; + } + else if( typeof(data[0]) != "number") //linearize: (transform Arrays in typed arrays) + { + var newdata = []; + for (var j = 0, chunk = 10000; j < data.length; j += chunk) { + newdata = Array.prototype.concat.apply(newdata, data.slice(j, j + chunk)); + } + data = newdata; + } + + var stream_info = GL.Mesh.common_buffers[i]; + + //cast to typed float32 if no type is specified + if(data.constructor === Array) + { + var datatype = GL.Mesh.default_datatype; + if(stream_info && stream_info.type) + datatype = stream_info.type; + data = new datatype( data ); + } + + //compute spacing + if(i == "vertices") + num_vertices = data.length / 3; + var spacing = data.length / num_vertices; + if(stream_info && stream_info.spacing) + spacing = stream_info.spacing; + + //add and upload + var attribute = "a_" + i; + if(stream_info && stream_info.attribute) + attribute = stream_info.attribute; + + if( this.vertexBuffers[i] ) + this.updateVertexBuffer( i, attribute, spacing, data, stream_type ); + else + this.createVertexBuffer( i, attribute, spacing, data, stream_type ); + } + + if(indexbuffers) + for(var i in indexbuffers) + { + var data = indexbuffers[i]; + if(!data) continue; + + if( data.constructor == GL.Buffer ) + { + data = data.data; + } + if( typeof(data[0]) != "number") //linearize + { + newdata = []; + for (var i = 0, chunk = 10000; i < data.length; i += chunk) { + newdata = Array.prototype.concat.apply(newdata, data.slice(i, i + chunk)); + } + data = newdata; + } + + //cast to typed + if(data.constructor === Array) + { + var datatype = Uint16Array; + if(num_vertices > 256*256) + datatype = Uint32Array; + data = new datatype( data ); + } + + this.createIndexBuffer( i, data ); + } +} + +/** +* Creates a new empty buffer and attachs it to this mesh +* @method createVertexBuffer +* @param {String} name "vertices","normals"... +* @param {String} attribute name of the stream in the shader "a_vertex","a_normal",... [optional, if omitted is used the common_buffers] +* @param {number} spacing components per vertex [optional, if ommited is used the common_buffers, if not found then uses 3 ] +* @param {ArrayBufferView} buffer_data the data in typed array format [optional, if ommited it created an empty array of getNumVertices() * spacing] +* @param {enum} stream_type [optional, default = gl.STATIC_DRAW (other: gl.DYNAMIC_DRAW, gl.STREAM_DRAW ) ] +*/ + +Mesh.prototype.createVertexBuffer = function( name, attribute, buffer_spacing, buffer_data, stream_type ) { + + var common = GL.Mesh.common_buffers[name]; //generic info about a buffer with the same name + + if (!attribute && common) + attribute = common.attribute; + + if (!attribute) + throw("Buffer added to mesh without attribute name"); + + if (!buffer_spacing && common) + { + if(common && common.spacing) + buffer_spacing = common.spacing; + else + buffer_spacing = 3; + } + + if(!buffer_data) + { + var num = this.getNumVertices(); + if(!num) + throw("Cannot create an empty buffer in a mesh without vertices (vertices are needed to know the size)"); + buffer_data = new (GL.Mesh.default_datatype)(num * buffer_spacing); + } + + if(!buffer_data.buffer) + throw("Buffer data MUST be typed array"); + + //used to ensure the buffers are held in the same gl context as the mesh + var buffer = this.vertexBuffers[name] = new GL.Buffer( gl.ARRAY_BUFFER, buffer_data, buffer_spacing, stream_type, this.gl ); + buffer.name = name; + buffer.attribute = attribute; + + return buffer; +} + +/** +* Updates a vertex buffer +* @method updateVertexBuffer +* @param {String} name the name of the buffer +* @param {String} attribute the name of the attribute in the shader +* @param {number} spacing number of numbers per component (3 per vertex, 2 per uvs...), default 3 +* @param {*} data the array with all the data +* @param {enum} stream_type default gl.STATIC_DRAW (other: gl.DYNAMIC_DRAW, gl.STREAM_DRAW +*/ +Mesh.prototype.updateVertexBuffer = function( name, attribute, buffer_spacing, buffer_data, stream_type ) { + var buffer = this.vertexBuffers[name]; + if(!buffer) + { + console.log("buffer not found: ",name); + return; + } + + if(!buffer_data.length) + return; + + buffer.attribute = attribute; + buffer.spacing = buffer_spacing; + buffer.data = buffer_data; + buffer.upload( stream_type ); +} + + +/** +* Removes a vertex buffer from the mesh +* @method removeVertexBuffer +* @param {String} name "vertices","normals"... +* @param {Boolean} free if you want to remove the data from the GPU +*/ +Mesh.prototype.removeVertexBuffer = function(name, free) { + var buffer = this.vertexBuffers[name]; + if(!buffer) + return; + if(free) + buffer.delete(); + delete this.vertexBuffers[name]; +} + +/** +* Returns a vertex buffer +* @method getVertexBuffer +* @param {String} name of vertex buffer +* @return {Buffer} the buffer +*/ +Mesh.prototype.getVertexBuffer = function(name) +{ + return this.vertexBuffers[name]; +} + + +/** +* Creates a new empty index buffer and attachs it to this mesh +* @method createIndexBuffer +* @param {String} name +* @param {Typed array} data +* @param {enum} stream_type gl.STATIC_DRAW, gl.DYNAMIC_DRAW, gl.STREAM_DRAW +*/ +Mesh.prototype.createIndexBuffer = function(name, buffer_data, stream_type) { + //(target, data, spacing, stream_type, gl) + + //cast to typed + if(buffer_data.constructor === Array) + { + var datatype = Uint16Array; + var vertices = this.vertexBuffers["vertices"]; + if(vertices) + { + var num_vertices = vertices.data.length / 3; + if(num_vertices > 256*256) + datatype = Uint32Array; + buffer_data = new datatype( buffer_data ); + } + } + + var buffer = this.indexBuffers[name] = new GL.Buffer(gl.ELEMENT_ARRAY_BUFFER, buffer_data, 0, stream_type, this.gl ); + return buffer; +} + +/** +* Returns a vertex buffer +* @method getBuffer +* @param {String} name of vertex buffer +* @return {Buffer} the buffer +*/ +Mesh.prototype.getBuffer = function(name) +{ + return this.vertexBuffers[name]; +} + +/** +* Returns a index buffer +* @method getIndexBuffer +* @param {String} name of index buffer +* @return {Buffer} the buffer +*/ +Mesh.prototype.getIndexBuffer = function(name) +{ + return this.indexBuffers[name]; +} + +/** +* Removes an index buffer from the mesh +* @method removeIndexBuffer +* @param {String} name "vertices","normals"... +* @param {Boolean} free if you want to remove the data from the GPU +*/ +Mesh.prototype.removeIndexBuffer = function(name, free) { + var buffer = this.indexBuffers[name]; + if(!buffer) + return; + if(free) + buffer.delete(); + delete this.indexBuffers[name]; +} + + +/** +* Uploads data inside buffers to VRAM. +* @method upload +* @param {number} buffer_type gl.STATIC_DRAW, gl.DYNAMIC_DRAW, gl.STREAM_DRAW +*/ +Mesh.prototype.upload = function(buffer_type) { + for (var attribute in this.vertexBuffers) { + var buffer = this.vertexBuffers[attribute]; + //buffer.data = this[buffer.name]; + buffer.upload(buffer_type); + } + + for (var name in this.indexBuffers) { + var buffer = this.indexBuffers[name]; + //buffer.data = this[name]; + buffer.upload(); + } +} + +//LEGACY, plz remove +Mesh.prototype.compile = Mesh.prototype.upload; + + +Mesh.prototype.deleteBuffers = function() +{ + for(var i in this.vertexBuffers) + { + var buffer = this.vertexBuffers[i]; + buffer.delete(); + } + this.vertexBuffers = {}; + + for(var i in this.indexBuffers) + { + var buffer = this.indexBuffers[i]; + buffer.delete(); + } + this.indexBuffers = {}; +} + +Mesh.prototype.delete = Mesh.prototype.deleteBuffers; + +Mesh.prototype.bindBuffers = function( shader ) +{ + // enable attributes as necessary. + for (var name in this.vertexBuffers) + { + var buffer = this.vertexBuffers[ name ]; + var attribute = buffer.attribute || name; + var location = shader.attributes[ attribute ]; + if (location == null || !buffer.buffer) + continue; + gl.bindBuffer(gl.ARRAY_BUFFER, buffer.buffer); + gl.enableVertexAttribArray(location); + gl.vertexAttribPointer(location, buffer.buffer.spacing, buffer.buffer.gl_type, false, 0, 0); + } +} + +Mesh.prototype.unbindBuffers = function( shader ) +{ + // disable attributes + for (var name in this.vertexBuffers) + { + var buffer = this.vertexBuffers[ name ]; + var attribute = buffer.attribute || name; + var location = shader.attributes[ attribute ]; + if (location == null || !buffer.buffer) + continue; //ignore this buffer + gl.disableVertexAttribArray( shader.attributes[attribute] ); + } +} + +/** +* Creates a clone of the mesh, the datarrays are cloned too +* @method clone +*/ +Mesh.prototype.clone = function( gl ) +{ + var gl = gl || global.gl; + var vbs = {}; + var ibs = {}; + + for(var i in this.vertexBuffers) + { + var b = this.vertexBuffers[i]; + vbs[i] = new b.data.constructor( b.data ); //clone + } + for(var i in this.indexBuffers) + { + var b = this.indexBuffers[i]; + ibs[i] = new b.data.constructor( b.data ); //clone + } + + return new GL.Mesh( vbs, ibs, undefined, gl ); +} + +/** +* Creates a clone of the mesh, but the data-arrays are shared between both meshes (useful for sharing a mesh between contexts) +* @method clone +*/ +Mesh.prototype.cloneShared = function( gl ) +{ + var gl = gl || global.gl; + return new GL.Mesh( this.vertexBuffers, this.indexBuffers, undefined, gl ); +} + + +/** +* Creates an object with the info of the mesh (useful to transfer to workers) +* @method toObject +*/ +Mesh.prototype.toObject = function() +{ + var vbs = {}; + var ibs = {}; + + for(var i in this.vertexBuffers) + { + var b = this.vertexBuffers[i]; + vbs[i] = { + spacing: b.spacing, + data: new b.data.constructor( b.data ) //clone + }; + } + for(var i in this.indexBuffers) + { + var b = this.indexBuffers[i]; + ibs[i] = { + data: new b.data.constructor( b.data ) //clone + } + } + + return { + vertexBuffers: vbs, + indexBuffers: ibs, + info: this.info ? cloneObject( this.info ) : null, + bounding: this._bounding.toJSON() + }; +} + + +Mesh.prototype.toJSON = function() +{ + var r = { + vertexBuffers: {}, + indexBuffers: {}, + info: this.info ? cloneObject( this.info ) : null, + bounding: this._bounding.toJSON() + }; + + for(var i in this.vertexBuffers) + r.vertexBuffers[i] = this.vertexBuffers[i].toJSON(); + + for(var i in this.indexBuffers) + r.indexBuffers[i] = this.indexBuffers[i].toJSON(); + + return r; +} + +Mesh.prototype.fromJSON = function(o) +{ + this.vertexBuffers = {}; + this.indexBuffers = {}; + + for(var i in o.vertexBuffers) + { + if(!o.vertexBuffers[i]) + continue; + var buffer = new GL.Buffer(); + buffer.fromJSON( o.vertexBuffers[i] ); + if(!buffer.attribute && GL.Mesh.common_buffers[i]) + buffer.attribute = GL.Mesh.common_buffers[i].attribute; + this.vertexBuffers[i] = buffer; + } + + for(var i in o.indexBuffers) + { + if(!o.indexBuffers[i]) + continue; + var buffer = new GL.Buffer(); + buffer.fromJSON( o.indexBuffers[i] ); + this.indexBuffers[i] = buffer; + } + + if(o.info) + this.info = cloneObject( o.info ); + if(o.bounding) + this.bounding = o.bounding; //setter does the job +} + + +/** +* Computes some data about the mesh +* @method generateMetadata +*/ +Mesh.prototype.generateMetadata = function() +{ + var metadata = {}; + + var vertices = this.vertexBuffers["vertices"].data; + var triangles = this.indexBuffers["triangles"].data; + + metadata.vertices = vertices.length / 3; + if(triangles) + metadata.faces = triangles.length / 3; + else + metadata.faces = vertices.length / 9; + + metadata.indexed = !!this.metadata.faces; + this.metadata = metadata; +} + +//never tested +/* +Mesh.prototype.draw = function(shader, mode, range_start, range_length) +{ + if(range_length == 0) return; + + // Create and enable attribute pointers as necessary. + var length = 0; + for (var attribute in this.vertexBuffers) { + var buffer = this.vertexBuffers[attribute]; + var location = shader.attributes[attribute] || + gl.getAttribLocation(shader.program, attribute); + if (location == -1 || !buffer.buffer) continue; + shader.attributes[attribute] = location; + gl.bindBuffer(gl.ARRAY_BUFFER, buffer.buffer); + gl.enableVertexAttribArray(location); + gl.vertexAttribPointer(location, buffer.buffer.spacing, gl.FLOAT, false, 0, 0); + length = buffer.buffer.length / buffer.buffer.spacing; + } + + //range rendering + var offset = 0; + if(arguments.length > 3) //render a polygon range + offset = range_start * (this.indexBuffer ? this.indexBuffer.constructor.BYTES_PER_ELEMENT : 1); //in bytes (Uint16 == 2 bytes) + + if(arguments.length > 4) + length = range_length; + else if (this.indexBuffer) + length = this.indexBuffer.buffer.length - offset; + + // Disable unused attribute pointers. + for (var attribute in shader.attributes) { + if (!(attribute in this.vertexBuffers)) { + gl.disableVertexAttribArray(shader.attributes[attribute]); + } + } + + // Draw the geometry. + if (length && (!this.indexBuffer || indexBuffer.buffer)) { + if (this.indexBuffer) { + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer.buffer); + gl.drawElements(mode, length, gl.UNSIGNED_SHORT, offset); + } else { + gl.drawArrays(mode, offset, length); + } + } + + return this; +} +*/ + +/** +* Creates a new index stream with wireframe +* @method computeWireframe +*/ +Mesh.prototype.computeWireframe = function() { + var index_buffer = this.indexBuffers["triangles"]; + + var vertices = this.vertexBuffers["vertices"].data; + var num_vertices = (vertices.length/3); + + if(!index_buffer) //unindexed + { + var num_triangles = num_vertices / 3; + var buffer = num_vertices > 256*256 ? new Uint32Array( num_triangles * 6 ) : new Uint16Array( num_triangles * 6 ); + for(var i = 0; i < num_vertices; i += 3) + { + buffer[i*2] = i; + buffer[i*2+1] = i+1; + buffer[i*2+2] = i+1; + buffer[i*2+3] = i+2; + buffer[i*2+4] = i+2; + buffer[i*2+5] = i; + } + + } + else //indexed + { + var data = index_buffer.data; + + var indexer = new GL.Indexer(); + for (var i = 0; i < data.length; i+=3) { + var t = data.subarray(i,i+3); + for (var j = 0; j < t.length; j++) { + var a = t[j], b = t[(j + 1) % t.length]; + indexer.add([Math.min(a, b), Math.max(a, b)]); + } + } + + //linearize + var unique = indexer.unique; + var buffer = num_vertices > 256*256 ? new Uint32Array( unique.length * 2 ) : new Uint16Array( unique.length * 2 ); + for(var i = 0, l = unique.length; i < l; ++i) + buffer.set(unique[i],i*2); + } + + //create stream + this.createIndexBuffer('wireframe', buffer); + return this; +} + + +/** +* Multiplies every normal by -1 and uploads it +* @method flipNormals +* @param {enum} stream_type default gl.STATIC_DRAW (other: gl.DYNAMIC_DRAW, gl.STREAM_DRAW) +*/ +Mesh.prototype.flipNormals = function( stream_type ) { + var normals_buffer = this.vertexBuffers["normals"]; + if(!normals_buffer) + return; + var data = normals_buffer.data; + var l = data.length; + for(var i = 0; i < l; ++i) + data[i] *= -1; + normals_buffer.upload( stream_type ); + + //reverse indices too + if( !this.indexBuffers["triangles"] ) + this.computeIndices(); //create indices + + var triangles_buffer = this.indexBuffers["triangles"]; + var data = triangles_buffer.data; + var l = data.length; + for(var i = 0; i < l; i += 3) + { + var tmp = data[i]; + data[i] = data[i+1]; + data[i+1] = tmp; + //the [i+2] stays the same + } + triangles_buffer.upload( stream_type ); +} + + +/** +* Compute indices for a mesh where vertices are shared +* @method computeIndices +*/ +Mesh.prototype.computeIndices = function() { + + //cluster by distance + var new_vertices = []; + var new_normals = []; + var new_coords = []; + + var indices = []; + + var old_vertices_buffer = this.vertexBuffers["vertices"]; + var old_normals_buffer = this.vertexBuffers["normals"]; + var old_coords_buffer = this.vertexBuffers["coords"]; + + var old_vertices_data = old_vertices_buffer.data; + + var old_normals_data = null; + if( old_normals_buffer ) + old_normals_data = old_normals_buffer.data; + + var old_coords_data = null; + if( old_coords_buffer ) + old_coords_data = old_coords_buffer.data; + + + var indexer = {}; + + var l = old_vertices_data.length / 3; + for(var i = 0; i < l; ++i) + { + var v = old_vertices_data.subarray( i*3,(i+1)*3 ); + var key = (v[0] * 1000)|0; + + //search in new_vertices + var j = 0; + var candidates = indexer[key]; + if(candidates) + { + var l2 = candidates.length; + for(; j < l2; j++) + { + var v2 = new_vertices[ candidates[j] ]; + //same vertex + if( vec3.sqrDist( v, v2 ) < 0.01 ) + { + indices.push(j); + break; + } + } + } + + /* + var l2 = new_vertices.length; + for(var j = 0; j < l2; j++) + { + //same vertex + if( vec3.sqrDist( v, new_vertices[j] ) < 0.001 ) + { + indices.push(j); + break; + } + } + */ + + if(candidates && j != l2) + continue; + + var index = j; + new_vertices.push(v); + if( indexer[ key ] ) + indexer[ key ].push( index ); + else + indexer[ key ] = [ index ]; + + if(old_normals_data) + new_normals.push( old_normals_data.subarray(i*3, (i+1)*3) ); + if(old_coords_data) + new_coords.push( old_coords_data.subarray(i*2, (i+1)*2) ); + indices.push(index); + } + + this.vertexBuffers = {}; //erase all + + //new buffers + this.createVertexBuffer( 'vertices', GL.Mesh.common_buffers["vertices"].attribute, 3, linearizeArray( new_vertices ) ); + if(old_normals_data) + this.createVertexBuffer( 'normals', GL.Mesh.common_buffers["normals"].attribute, 3, linearizeArray( new_normals ) ); + if(old_coords_data) + this.createVertexBuffer( 'coords', GL.Mesh.common_buffers["coords"].attribute, 2, linearizeArray( new_coords ) ); + + this.createIndexBuffer( "triangles", indices ); +} + +/** +* Breaks the indices +* @method explodeIndices +*/ +Mesh.prototype.explodeIndices = function( buffer_name ) { + + buffer_name = buffer_name || "triangles"; + + var indices_buffer = this.getIndexBuffer( buffer_name ); + if(!indices_buffer) + return; + + var indices = indices_buffer.data; + + var new_buffers = {}; + for(var i in this.vertexBuffers) + { + var info = GL.Mesh.common_buffers[i]; + new_buffers[i] = new (info.type || Float32Array)( info.spacing * indices.length ); + } + + for(var i = 0, l = indices.length; i < l; ++i) + { + var index = indices[i]; + for(var j in this.vertexBuffers) + { + var buffer = this.vertexBuffers[j]; + var info = GL.Mesh.common_buffers[j]; + var spacing = buffer.spacing || info.spacing; + var new_buffer = new_buffers[j]; + new_buffer.set( buffer.data.subarray( index*spacing, index*spacing + spacing ), i*spacing ); + } + } + + /* + //cluster by distance + var new_vertices = new Float32Array(indices.length * 3); + var new_normals = null; + var new_coords = null; + + var old_vertices_buffer = this.vertexBuffers["vertices"]; + var old_vertices = old_vertices_buffer.data; + + var old_normals_buffer = this.vertexBuffers["normals"]; + var old_normals = null; + if(old_normals_buffer) + { + old_normals = old_normals_buffer.data; + new_normals = new Float32Array(indices.length * 3); + } + + var old_coords_buffer = this.vertexBuffers["coords"]; + var old_coords = null; + if( old_coords_buffer ) + { + old_coords = old_coords_buffer.data; + new_coords = new Float32Array(indices.length * 2); + } + + for(var i = 0, l = indices.length; i < l; ++i) + { + var index = indices[i]; + new_vertices.set( old_vertices.subarray( index*3, index*3 + 3 ), i*3 ); + if(old_normals) + new_normals.set( old_normals.subarray( index*3, index*3 + 3 ), i*3 ); + if(old_coords) + new_coords.set( old_coords.subarray( index*2, index*2 + 2 ), i*2 ); + } + + //erase all + this.vertexBuffers = {}; + + //new buffers + this.createVertexBuffer( 'vertices', GL.Mesh.common_buffers["vertices"].attribute, 3, new_vertices ); + if(new_normals) + this.createVertexBuffer( 'normals', GL.Mesh.common_buffers["normals"].attribute, 3, new_normals ); + if(new_coords) + this.createVertexBuffer( 'coords', GL.Mesh.common_buffers["coords"].attribute, 2, new_coords ); + */ + + for(var i in new_buffers) + { + var old = this.vertexBuffers[i]; + this.createVertexBuffer( i, old.attribute, old.spacing, new_buffers[i] ); + } + + delete this.indexBuffers[ buffer_name ]; +} + + + +/** +* Creates a stream with the normals +* @method computeNormals +* @param {enum} stream_type default gl.STATIC_DRAW (other: gl.DYNAMIC_DRAW, gl.STREAM_DRAW) +*/ +Mesh.prototype.computeNormals = function( stream_type ) { + var vertices_buffer = this.vertexBuffers["vertices"]; + if(!vertices_buffer) + return console.error("Cannot compute normals of a mesh without vertices"); + + var vertices = this.vertexBuffers["vertices"].data; + var num_vertices = vertices.length / 3; + + //create because it is faster than filling it with zeros + var normals = new Float32Array( vertices.length ); + + var triangles = null; + if(this.indexBuffers["triangles"]) + triangles = this.indexBuffers["triangles"].data; + + var temp = GL.temp_vec3; + var temp2 = GL.temp2_vec3; + + var i1,i2,i3,v1,v2,v3,n1,n2,n3; + + //compute the plane normal + var l = triangles ? triangles.length : vertices.length; + for (var a = 0; a < l; a+=3) + { + if(triangles) + { + i1 = triangles[a]; + i2 = triangles[a+1]; + i3 = triangles[a+2]; + + v1 = vertices.subarray(i1*3,i1*3+3); + v2 = vertices.subarray(i2*3,i2*3+3); + v3 = vertices.subarray(i3*3,i3*3+3); + + n1 = normals.subarray(i1*3,i1*3+3); + n2 = normals.subarray(i2*3,i2*3+3); + n3 = normals.subarray(i3*3,i3*3+3); + } + else + { + v1 = vertices.subarray(a*3,a*3+3); + v2 = vertices.subarray(a*3+3,a*3+6); + v3 = vertices.subarray(a*3+6,a*3+9); + + n1 = normals.subarray(a*3,a*3+3); + n2 = normals.subarray(a*3+3,a*3+6); + n3 = normals.subarray(a*3+6,a*3+9); + } + + vec3.sub( temp, v2, v1 ); + vec3.sub( temp2, v3, v1 ); + vec3.cross( temp, temp, temp2 ); + vec3.normalize(temp,temp); + + //save + vec3.add( n1, n1, temp ); + vec3.add( n2, n2, temp ); + vec3.add( n3, n3, temp ); + } + + //normalize if vertices are shared + if(triangles) + for (var a = 0, l = normals.length; a < l; a+=3) + { + var n = normals.subarray(a,a+3); + vec3.normalize(n,n); + } + + var normals_buffer = this.vertexBuffers["normals"]; + + if(normals_buffer) + { + normals_buffer.data = normals; + normals_buffer.upload( stream_type ); + } + else + return this.createVertexBuffer('normals', GL.Mesh.common_buffers["normals"].attribute, 3, normals ); + return normals_buffer; +} + + +/** +* Creates a new stream with the tangents +* @method computeTangents +*/ +Mesh.prototype.computeTangents = function() +{ + var vertices_buffer = this.vertexBuffers["vertices"]; + if(!vertices_buffer) + return console.error("Cannot compute tangents of a mesh without vertices"); + + var normals_buffer = this.vertexBuffers["normals"]; + if(!normals_buffer) + return console.error("Cannot compute tangents of a mesh without normals"); + + var uvs_buffer = this.vertexBuffers["coords"]; + if(!uvs_buffer) + return console.error("Cannot compute tangents of a mesh without uvs"); + + var triangles_buffer = this.indexBuffers["triangles"]; + if(!triangles_buffer) + return console.error("Cannot compute tangents of a mesh without indices"); + + var vertices = vertices_buffer.data; + var normals = normals_buffer.data; + var uvs = uvs_buffer.data; + var triangles = triangles_buffer.data; + + if(!vertices || !normals || !uvs) return; + + var num_vertices = vertices.length / 3; + + var tangents = new Float32Array(num_vertices * 4); + + //temporary (shared) + var tan1 = new Float32Array(num_vertices*3*2); + var tan2 = tan1.subarray(num_vertices*3); + + var a,l; + var sdir = vec3.create(); + var tdir = vec3.create(); + var temp = vec3.create(); + var temp2 = vec3.create(); + + for (a = 0, l = triangles.length; a < l; a+=3) + { + var i1 = triangles[a]; + var i2 = triangles[a+1]; + var i3 = triangles[a+2]; + + var v1 = vertices.subarray(i1*3,i1*3+3); + var v2 = vertices.subarray(i2*3,i2*3+3); + var v3 = vertices.subarray(i3*3,i3*3+3); + + var w1 = uvs.subarray(i1*2,i1*2+2); + var w2 = uvs.subarray(i2*2,i2*2+2); + var w3 = uvs.subarray(i3*2,i3*2+2); + + var x1 = v2[0] - v1[0]; + var x2 = v3[0] - v1[0]; + var y1 = v2[1] - v1[1]; + var y2 = v3[1] - v1[1]; + var z1 = v2[2] - v1[2]; + var z2 = v3[2] - v1[2]; + + var s1 = w2[0] - w1[0]; + var s2 = w3[0] - w1[0]; + var t1 = w2[1] - w1[1]; + var t2 = w3[1] - w1[1]; + + var r; + var den = (s1 * t2 - s2 * t1); + if ( Math.abs(den) < 0.000000001 ) + r = 0.0; + else + r = 1.0 / den; + + vec3.copy(sdir, [(t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, (t2 * z1 - t1 * z2) * r] ); + vec3.copy(tdir, [(s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, (s1 * z2 - s2 * z1) * r] ); + + vec3.add( tan1.subarray( i1*3, i1*3+3), tan1.subarray( i1*3, i1*3+3), sdir); + vec3.add( tan1.subarray( i2*3, i2*3+3), tan1.subarray( i2*3, i2*3+3), sdir); + vec3.add( tan1.subarray( i3*3, i3*3+3), tan1.subarray( i3*3, i3*3+3), sdir); + + vec3.add( tan2.subarray( i1*3, i1*3+3), tan2.subarray( i1*3, i1*3+3), tdir); + vec3.add( tan2.subarray( i2*3, i2*3+3), tan2.subarray( i2*3, i2*3+3), tdir); + vec3.add( tan2.subarray( i3*3, i3*3+3), tan2.subarray( i3*3, i3*3+3), tdir); + } + + for (a = 0, l = vertices.length; a < l; a+=3) + { + var n = normals.subarray(a,a+3); + var t = tan1.subarray(a,a+3); + + // Gram-Schmidt orthogonalize + vec3.subtract(temp, t, vec3.scale(temp, n, vec3.dot(n, t) ) ); + vec3.normalize(temp,temp); + + // Calculate handedness + var w = ( vec3.dot( vec3.cross(temp2, n, t), tan2.subarray(a,a+3) ) < 0.0) ? -1.0 : 1.0; + tangents.set([temp[0], temp[1], temp[2], w],(a/3)*4); + } + + this.createVertexBuffer('tangents', Mesh.common_buffers["tangents"].attribute, 4, tangents ); +} + +/** +* Creates texture coordinates using a triplanar aproximation +* @method computeTextureCoordinates +*/ +Mesh.prototype.computeTextureCoordinates = function( stream_type ) +{ + var vertices_buffer = this.vertexBuffers["vertices"]; + if(!vertices_buffer) + return console.error("Cannot compute uvs of a mesh without vertices"); + + this.explodeIndices( "triangles" ); + + vertices_buffer = this.vertexBuffers["vertices"]; + var vertices = vertices_buffer.data; + var num_vertices = vertices.length / 3; + + var uvs_buffer = this.vertexBuffers["coords"]; + var uvs = new Float32Array( num_vertices * 2 ); + + var triangles_buffer = this.indexBuffers["triangles"]; + var triangles = null; + if( triangles_buffer ) + triangles = triangles_buffer.data; + + var plane_normal = vec3.create(); + var side1 = vec3.create(); + var side2 = vec3.create(); + + var bbox = this.getBoundingBox(); + var bboxcenter = BBox.getCenter( bbox ); + var bboxhs = vec3.create(); + bboxhs.set( BBox.getHalfsize( bbox ) ); //careful, this is a reference + vec3.scale( bboxhs, bboxhs, 2 ); + + var num = triangles ? triangles.length : vertices.length/3; + + for (var a = 0; a < num; a+=3) + { + if(triangles) + { + var i1 = triangles[a]; + var i2 = triangles[a+1]; + var i3 = triangles[a+2]; + + var v1 = vertices.subarray(i1*3,i1*3+3); + var v2 = vertices.subarray(i2*3,i2*3+3); + var v3 = vertices.subarray(i3*3,i3*3+3); + + var uv1 = uvs.subarray(i1*2,i1*2+2); + var uv2 = uvs.subarray(i2*2,i2*2+2); + var uv3 = uvs.subarray(i3*2,i3*2+2); + } + else + { + var v1 = vertices.subarray((a)*3,(a)*3+3); + var v2 = vertices.subarray((a+1)*3,(a+1)*3+3); + var v3 = vertices.subarray((a+2)*3,(a+2)*3+3); + + var uv1 = uvs.subarray((a)*2,(a)*2+2); + var uv2 = uvs.subarray((a+1)*2,(a+1)*2+2); + var uv3 = uvs.subarray((a+2)*2,(a+2)*2+2); + } + + vec3.sub(side1, v1, v2 ); + vec3.sub(side2, v1, v3 ); + vec3.cross( plane_normal, side1, side2 ); + //vec3.normalize( plane_normal, plane_normal ); //not necessary + + plane_normal[0] = Math.abs( plane_normal[0] ); + plane_normal[1] = Math.abs( plane_normal[1] ); + plane_normal[2] = Math.abs( plane_normal[2] ); + + if( plane_normal[0] > plane_normal[1] && plane_normal[0] > plane_normal[2]) + { + //X + uv1[0] = (v1[2] - bboxcenter[2]) / bboxhs[2]; + uv1[1] = (v1[1] - bboxcenter[1]) / bboxhs[1]; + uv2[0] = (v2[2] - bboxcenter[2]) / bboxhs[2]; + uv2[1] = (v2[1] - bboxcenter[1]) / bboxhs[1]; + uv3[0] = (v3[2] - bboxcenter[2]) / bboxhs[2]; + uv3[1] = (v3[1] - bboxcenter[1]) / bboxhs[1]; + } + else if ( plane_normal[1] > plane_normal[2]) + { + //Y + uv1[0] = (v1[0] - bboxcenter[0]) / bboxhs[0]; + uv1[1] = (v1[2] - bboxcenter[2]) / bboxhs[2]; + uv2[0] = (v2[0] - bboxcenter[0]) / bboxhs[0]; + uv2[1] = (v2[2] - bboxcenter[2]) / bboxhs[2]; + uv3[0] = (v3[0] - bboxcenter[0]) / bboxhs[0]; + uv3[1] = (v3[2] - bboxcenter[2]) / bboxhs[2]; + } + else + { + //Z + uv1[0] = (v1[0] - bboxcenter[0]) / bboxhs[0]; + uv1[1] = (v1[1] - bboxcenter[1]) / bboxhs[1]; + uv2[0] = (v2[0] - bboxcenter[0]) / bboxhs[0]; + uv2[1] = (v2[1] - bboxcenter[1]) / bboxhs[1]; + uv3[0] = (v3[0] - bboxcenter[0]) / bboxhs[0]; + uv3[1] = (v3[1] - bboxcenter[1]) / bboxhs[1]; + } + } + + if(uvs_buffer) + { + uvs_buffer.data = uvs; + uvs_buffer.upload( stream_type ); + } + else + this.createVertexBuffer('coords', Mesh.common_buffers["coords"].attribute, 2, uvs ); +} + + +/** +* Computes the number of vertices +* @method getVertexNumber +*/ +Mesh.prototype.getNumVertices = function() { + var b = this.vertexBuffers["vertices"]; + if(!b) + return 0; + return b.data.length / b.spacing; +} + +/** +* Computes the number of triangles (takes into account indices) +* @method getNumTriangles +*/ +Mesh.prototype.getNumTriangles = function() { + var indices_buffer = this.getIndexBuffer("triangles"); + if(!indices_buffer) + return this.getNumVertices() / 3; + return indices_buffer.data.length / 3; +} + + +/** +* Computes bounding information +* @method Mesh.computeBoundingBox +* @param {typed Array} vertices array containing all the vertices +* @param {BBox} bb where to store the bounding box +* @param {Array} mask [optional] to specify which vertices must be considered when creating the bbox, used to create BBox of a submesh +*/ +Mesh.computeBoundingBox = function( vertices, bb, mask ) { + + if(!vertices) + return; + + var start = 0; + + if(mask) + { + for(var i = 0; i < mask.length; ++i) + if( mask[i] ) + { + start = i; + break; + } + if(start == mask.length) + { + console.warn("mask contains only zeros, no vertices marked"); + return; + } + } + + var min = vec3.clone( vertices.subarray( start*3, start*3 + 3) ); + var max = vec3.clone( vertices.subarray( start*3, start*3 + 3) ); + var v; + + for(var i = start*3; i < vertices.length; i+=3) + { + if( mask && !mask[i/3] ) + continue; + v = vertices.subarray(i,i+3); + vec3.min( min,v, min); + vec3.max( max,v, max); + } + + if( isNaN(min[0]) || isNaN(min[1]) || isNaN(min[2]) || + isNaN(max[0]) || isNaN(max[1]) || isNaN(max[2]) ) + { + min[0] = min[1] = min[2] = 0; + max[0] = max[1] = max[2] = 0; + console.warn("Warning: GL.Mesh has NaN values in vertices"); + } + + var center = vec3.add( vec3.create(), min,max ); + vec3.scale( center, center, 0.5); + var half_size = vec3.subtract( vec3.create(), max, center ); + + return BBox.setCenterHalfsize( bb || BBox.create(), center, half_size ); +} + +/** +* returns the bounding box, if it is not computed, then computes it +* @method getBoundingBox +* @return {BBox} bounding box +*/ +Mesh.prototype.getBoundingBox = function() +{ + if(this._bounding) + return this._bounding; + + this.updateBoundingBox(); + return this._bounding; +} + +/** +* Update bounding information of this mesh +* @method updateBoundingBox +*/ +Mesh.prototype.updateBoundingBox = function() { + var vertices = this.vertexBuffers["vertices"]; + if(!vertices) + return; + GL.Mesh.computeBoundingBox( vertices.data, this._bounding ); + if(this.info && this.info.groups && this.info.groups.length) + this.computeGroupsBoundingBoxes(); +} + +/** +* Update bounding information for every group submesh +* @method computeGroupsBoundingBoxes +*/ +Mesh.prototype.computeGroupsBoundingBoxes = function() +{ + var indices = null; + var indices_buffer = this.getIndexBuffer("triangles"); + if( indices_buffer ) + indices = indices_buffer.data; + + var vertices_buffer = this.getVertexBuffer("vertices"); + if(!vertices_buffer) + return false; + var vertices = vertices_buffer.data; + if(!vertices.length) + return false; + + var groups = this.info.groups; + for(var i = 0; i < groups.length; ++i) + { + var group = groups[i]; + group.bounding = group.bounding || BBox.create(); + var submesh_vertices = null; + if( indices ) + { + var mask = new Uint8Array( vertices.length / 3 ); + var s = group.start; + for( var j = 0, l = group.length; j < l; j += 3 ) + { + mask[ indices[s+j] ] = 1; + mask[ indices[s+j+1] ] = 1; + mask[ indices[s+j+2] ] = 1; + } + GL.Mesh.computeBoundingBox( vertices, group.bounding, mask ); + } + else + { + submesh_vertices = vertices.subarray( group.start * 3, ( group.start + group.length) * 3 ); + GL.Mesh.computeBoundingBox( submesh_vertices, group.bounding ); + } + } + return true; +} + + + +/** +* forces a bounding box to be set +* @method setBoundingBox +* @param {vec3} center center of the bounding box +* @param {vec3} half_size vector from the center to positive corner +*/ +Mesh.prototype.setBoundingBox = function( center, half_size ) { + BBox.setCenterHalfsize( this._bounding, center, half_size ); +} + + +/** +* Remove all local memory from the streams (leaving it only in the VRAM) to save RAM +* @method freeData +*/ +Mesh.prototype.freeData = function() +{ + for (var attribute in this.vertexBuffers) + { + this.vertexBuffers[attribute].data = null; + delete this[ this.vertexBuffers[attribute].name ]; //delete from the mesh itself + } + for (var name in this.indexBuffers) + { + this.indexBuffers[name].data = null; + delete this[ this.indexBuffers[name].name ]; //delete from the mesh itself + } +} + +Mesh.prototype.configure = function( o, options ) +{ + var vertex_buffers = {}; + var index_buffers = {}; + options = options || {}; + + for(var j in o) + { + if(!o[j]) + continue; + + if(j == "vertexBuffers" || j == "vertex_buffers") //HACK: legacy code + { + for(i in o[j]) + vertex_buffers[i] = o[j][i]; + continue; + } + + if(j == "indexBuffers" || j == "index_buffers") + { + for(i in o[j]) + index_buffers[i] = o[j][i]; + continue; + } + + if(j == "indices" || j == "lines" || j == "wireframe" || j == "triangles") + index_buffers[j] = o[j]; + else if( GL.Mesh.common_buffers[j]) + vertex_buffers[j] = o[j]; + else //global data like bounding, info of groups, etc + { + options[j] = o[j]; + } + } + + this.addBuffers( vertex_buffers, index_buffers, options.stream_type ); + + for(var i in options) + this[i] = options[i]; + + if(!options.bounding) + this.updateBoundingBox(); +} + +/** +* Returns the amount of memory used by this mesh in bytes (sum of all buffers) +* @method getMemory +* @return {number} bytes +*/ +Mesh.prototype.totalMemory = function() +{ + var num = 0|0; + + for (var name in this.vertexBuffers) + num += this.vertexBuffers[name].data.buffer.byteLength; + for (var name in this.indexBuffers) + num += this.indexBuffers[name].data.buffer.byteLength; + + return num; +} + +Mesh.prototype.slice = function(start, length) +{ + var new_vertex_buffers = {}; + + var indices_buffer = this.indexBuffers["triangles"]; + if(!indices_buffer) + { + console.warn("splice in not indexed not supported yet"); + return null; + } + + var indices = indices_buffer.data; + + var new_triangles = []; + var reindex = new Int32Array( indices.length ); + reindex.fill(-1); + + var end = start + length; + if(end >= indices.length) + end = indices.length; + + var last_index = 0; + for(var j = start; j < end; ++j) + { + var index = indices[j]; + if( reindex[index] != -1 ) + { + new_triangles.push(reindex[index]); + continue; + } + + //new vertex + var new_index = last_index++; + reindex[index] = new_index; + new_triangles.push(new_index); + + for( var i in this.vertexBuffers ) + { + var buffer = this.vertexBuffers[i]; + var data = buffer.data; + var spacing = buffer.spacing; + if(!new_vertex_buffers[i]) + new_vertex_buffers[i] = []; + var new_buffer = new_vertex_buffers[i]; + for(var k = 0; k < spacing; ++k) + new_buffer.push( data[k + index*spacing] ); + } + } + + var new_mesh = new GL.Mesh( new_vertex_buffers, {triangles: new_triangles}, null,gl); + new_mesh.updateBoundingBox(); + return new_mesh; +} + + +/** +* returns a low poly version of the mesh that takes much less memory (but breaks tiling of uvs and smoothing groups) +* @method simplify +* @return {Mesh} simplified mesh +*/ +Mesh.prototype.simplify = function() +{ + //compute bounding box + var bb = this.getBoundingBox(); + var min = BBox.getMin( bb ); + var halfsize = BBox.getHalfsize( bb ); + var range = vec3.scale( vec3.create(), halfsize, 2 ); + + var newmesh = new GL.Mesh(); + var temp = vec3.create(); + + for(var i in this.vertexBuffers) + { + //take every vertex and normalize it to the bounding box + var buffer = this.vertexBuffers[i]; + var data = buffer.data; + + var new_data = new Float32Array( data.length ); + + if(i == "vertices") + { + for(var j = 0, l = data.length; j < l; j+=3 ) + { + var v = data.subarray(j,j+3); + vec3.sub( temp, v, min ); + vec3.div( temp, temp, range ); + temp[0] = Math.round(temp[0] * 256) / 256; + temp[1] = Math.round(temp[1] * 256) / 256; + temp[2] = Math.round(temp[2] * 256) / 256; + vec3.mul( temp, temp, range ); + vec3.add( temp, temp, min ); + new_data.set( temp, j ); + } + } + else + { + } + + newmesh.addBuffer(); + } + + //search for repeated vertices + //compute the average normal and coord + //reindex the triangles + //return simplified mesh +} + +/** +* Static method for the class Mesh to create a mesh from a list of common streams +* @method Mesh.load +* @param {Object} buffers object will all the buffers +* @param {Object} options [optional] +* @param {Mesh} output_mesh [optional] mesh to store the mesh, otherwise is created +* @param {WebGLContext} gl [optional] if omitted, the global.gl is used +*/ +Mesh.load = function( buffers, options, output_mesh, gl ) { + options = options || {}; + if(options.no_gl) + gl = null; + var mesh = output_mesh || new GL.Mesh(null,null,null,gl); + mesh.configure( buffers, options ); + return mesh; +} + +/** +* Returns a mesh with all the meshes merged (you can apply transforms individually to every buffer) +* @method Mesh.mergeMeshes +* @param {Array} meshes array containing object like { mesh:, matrix:, texture_matrix: } +* @param {Object} options { only_data: to get the mesh data without uploading it } +* @return {GL.Mesh|Object} the mesh in GL.Mesh format or Object format (if options.only_data is true) +*/ +Mesh.mergeMeshes = function( meshes, options ) +{ + options = options || {}; + + var vertex_buffers = {}; + var index_buffers = {}; + var offsets = {}; //tells how many positions indices must be offseted + var vertex_offsets = []; + var current_vertex_offset = 0; + var groups = []; + + //vertex buffers + //compute size + for(var i = 0; i < meshes.length; ++i) + { + var mesh_info = meshes[i]; + var mesh = mesh_info.mesh; + var offset = current_vertex_offset; + vertex_offsets.push( offset ); + var length = mesh.vertexBuffers["vertices"].data.length / 3; + current_vertex_offset += length; + + for(var j in mesh.vertexBuffers) + { + if(!vertex_buffers[j]) + vertex_buffers[j] = mesh.vertexBuffers[j].data.length; + else + vertex_buffers[j] += mesh.vertexBuffers[j].data.length; + } + + for(var j in mesh.indexBuffers) + { + if(!index_buffers[j]) + index_buffers[j] = mesh.indexBuffers[j].data.length; + else + index_buffers[j] += mesh.indexBuffers[j].data.length; + } + + //groups + var group = { + name: "mesh_" + i, + start: offset, + length: length, + material: "" + }; + + groups.push( group ); + } + + //allocate + for(var j in vertex_buffers) + { + var datatype = options[j]; + if(datatype === null) + { + delete vertex_buffers[j]; + continue; + } + + if(!datatype) + datatype = Float32Array; + + vertex_buffers[j] = new datatype( vertex_buffers[j] ); + offsets[j] = 0; + } + + for(var j in index_buffers) + { + index_buffers[j] = new Uint32Array( index_buffers[j] ); + offsets[j] = 0; + } + + //store + for(var i = 0; i < meshes.length; ++i) + { + var mesh_info = meshes[i]; + var mesh = mesh_info.mesh; + var offset = 0; + var length = 0; + + for(var j in mesh.vertexBuffers) + { + if(!vertex_buffers[j]) + continue; + + if(j == "vertices") + length = mesh.vertexBuffers[j].data.length / 3; + + vertex_buffers[j].set( mesh.vertexBuffers[j].data, offsets[j] ); + + //apply transform + if(mesh_info[ j + "_matrix"] ) + { + var matrix = mesh_info[ j + "_matrix" ]; + if(matrix.length == 16) + apply_transform( vertex_buffers[j], offsets[j], mesh.vertexBuffers[j].data.length, matrix ) + else if(matrix.length == 9) + apply_transform2D( vertex_buffers[j], offsets[j], mesh.vertexBuffers[j].data.length, matrix ) + } + + offsets[j] += mesh.vertexBuffers[j].data.length; + } + + for(var j in mesh.indexBuffers) + { + index_buffers[j].set( mesh.indexBuffers[j].data, offsets[j] ); + apply_offset( index_buffers[j], offsets[j], mesh.indexBuffers[j].data.length, vertex_offsets[i] ); + offsets[j] += mesh.indexBuffers[j].data.length; + } + } + + //useful functions + function apply_transform( array, start, length, matrix ) + { + var l = start + length; + for(var i = start; i < l; i+=3) + { + var v = array.subarray(i,i+3); + vec3.transformMat4( v, v, matrix ); + } + } + + function apply_transform2D( array, start, length, matrix ) + { + var l = start + length; + for(var i = start; i < l; i+=2) + { + var v = array.subarray(i,i+2); + vec2.transformMat3( v, v, matrix ); + } + } + + function apply_offset( array, start, length, offset ) + { + var l = start + length; + for(var i = start; i < l; ++i) + array[i] += offset; + } + + var extra = { info: { groups: groups } }; + + //return + if( typeof(gl) != "undefined" || options.only_data ) + return new GL.Mesh( vertex_buffers,index_buffers, extra ); + return { + vertexBuffers: vertex_buffers, + indexBuffers: index_buffers, + info: { groups: groups } + }; +} + + + +//Here we store all basic mesh parsers (OBJ, STL) and encoders +Mesh.parsers = {}; +Mesh.encoders = {}; +Mesh.binary_file_formats = {}; //extensions that must be downloaded in binary +Mesh.compressors = {}; //used to compress binary meshes +Mesh.decompressors = {}; //used to decompress binary meshes + +/** +* Returns am empty mesh and loads a mesh and parses it using the Mesh.parsers, by default only OBJ is supported +* @method Mesh.fromOBJ +* @param {Array} meshes array containing all the meshes +*/ +Mesh.fromURL = function(url, on_complete, gl, options) +{ + options = options || {}; + gl = gl || global.gl; + + var mesh = new GL.Mesh(undefined,undefined,undefined,gl); + mesh.ready = false; + + var pos = url.lastIndexOf("."); + var extension = url.substr(pos+1).toLowerCase(); + options.binary = Mesh.binary_file_formats[ extension ]; + + HttpRequest( url, null, function(data) { + mesh.parse( data, extension ); + delete mesh["ready"]; + if(on_complete) + on_complete.call(mesh,mesh, url); + }, function(err){ + if(on_complete) + on_complete(null); + }, options ); + return mesh; +} + +/** +* given some data an information about the format, it search for a parser in Mesh.parsers and tries to extract the mesh information +* Only obj supported now +* @method parse +* @param {*} data could be string or ArrayBuffer +* @param {String} format parser file format name (p.e. "obj") +* @return {?} depending on the parser +*/ +Mesh.prototype.parse = function( data, format ) +{ + format = format.toLowerCase(); + var parser = GL.Mesh.parsers[ format ]; + if(parser) + return parser.call(null, data, {mesh: this}); + throw("GL.Mesh.parse: no parser found for format " + format ); +} + +/** +* It returns the mesh data encoded in the format specified +* Only obj supported now +* @method encode +* @param {String} format to encode the data to (p.e. "obj") +* @return {?} String with the info +*/ +Mesh.prototype.encode = function( format, options ) +{ + format = format.toLowerCase(); + var encoder = GL.Mesh.encoders[ format ]; + if(encoder) + return encoder.call(null, this, options ); + throw("GL.Mesh.encode: no encoder found for format " + format ); +} + +/** +* Returns a shared mesh containing a quad to be used when rendering to the screen +* Reusing the same quad helps not filling the memory +* @method getScreenQuad +* @return {GL.Mesh} the screen quad +*/ +Mesh.getScreenQuad = function(gl) +{ + gl = gl || global.gl; + var mesh = gl.meshes[":screen_quad"]; + if(mesh) + return mesh; + + var vertices = new Float32Array([0,0,0, 1,1,0, 0,1,0, 0,0,0, 1,0,0, 1,1,0 ]); + var coords = new Float32Array([0,0, 1,1, 0,1, 0,0, 1,0, 1,1 ]); + mesh = new GL.Mesh({ vertices: vertices, coords: coords}, undefined, undefined, gl); + return gl.meshes[":screen_quad"] = mesh; +} + +function linearizeArray( array, typed_array_class ) +{ + if(array.constructor === typed_array_class) + return array; + if(array.constructor !== Array) + { + typed_array_class = typed_array_class || Float32Array; + return new typed_array_class(array); + } + + typed_array_class = typed_array_class || Float32Array; + var components = array[0].length; + var size = array.length * components; + var buffer = new typed_array_class(size); + + for (var i=0; i < array.length;++i) + for(var j=0; j < components; ++j) + buffer[i*components + j] = array[i][j]; + return buffer; +} + +GL.linearizeArray = linearizeArray; + +/* BINARY MESHES */ +//Add some functions to the classes in LiteGL to allow store in binary +GL.Mesh.EXTENSION = "wbin"; +GL.Mesh.enable_wbin_compression = true; + +//this is used when a mesh is dynamic and constantly changes +function DynamicMesh( size, normals, coords, colors, gl ) +{ + size = size || 1024; + + if(GL.debug) + console.log("GL.Mesh created"); + + if( gl !== null ) + { + gl = gl || global.gl; + this.gl = gl; + } + + //used to avoid problems with resources moving between different webgl context + this._context_id = gl.context_id; + + this.vertexBuffers = {}; + this.indexBuffers = {}; + + //here you can store extra info, like groups, which is an array of { name, start, length, material } + this.info = { + groups: [] + }; + this._bounding = BBox.create(); //here you can store a AABB in BBox format + + this.resize( size ); +} + +DynamicMesh.DEFAULT_NORMAL = vec3.fromValues(0,1,0); +DynamicMesh.DEFAULT_COORD = vec2.fromValues(0.5,0.5); +DynamicMesh.DEFAULT_COLOR = vec4.fromValues(1,1,1,1); + +DynamicMesh.prototype.resize = function( size ) +{ + var buffers = {}; + + this._vertex_data = new Float32Array( size * 3 ); + buffers.vertices = this._vertex_data; + + if( normals ) + buffers.normals = this._normal_data = new Float32Array( size * 3 ); + if( coords ) + buffers.coords = this._coord_data = new Float32Array( size * 2 ); + if( colors ) + buffers.colors = this._color_data = new Float32Array( size * 4 ); + + this.addBuffers( buffers ); + + this.current_pos = 0; + this.max_size = size; + this._must_update = true; +} + +DynamicMesh.prototype.clear = function() +{ + this.current_pos = 0; +} + +DynamicMesh.prototype.addPoint = function( vertex, normal, coord, color ) +{ + if (pos >= this.max_size) + { + console.warn("DynamicMesh: not enough space, reserve more"); + return false; + } + var pos = this.current_pos++; + + this._vertex_data.set( vertex, pos*3 ); + + if(this._normal_data) + this._normal_data.set( normal || DynamicMesh.DEFAULT_NORMAL, pos*3 ); + if(this._coord_data) + this._coord_data.set( coord || DynamicMesh.DEFAULT_COORD, pos*2 ); + if(this._color_data) + this._color_data.set( color || DynamicMesh.DEFAULT_COLOR, pos*4 ); + + this._must_update = true; + return true; +} + +DynamicMesh.prototype.update = function( force ) +{ + if(!this._must_update && !force) + return this.current_pos; + this._must_update = false; + + this.getBuffer("vertices").upload( gl.STREAM_DRAW ); + if(this._normal_data) + this.getBuffer("normal").upload( gl.STREAM_DRAW ); + if(this._coord_data) + this.getBuffer("coord").upload( gl.STREAM_DRAW ); + if(this._color_data) + this.getBuffer("color").upload( gl.STREAM_DRAW ); + return this.current_pos; +} + +extendClass( DynamicMesh, Mesh ); + +/** +* @class Mesh +*/ + +/** +* Returns a planar mesh (you can choose how many subdivisions) +* @method Mesh.plane +* @param {Object} options valid options: detail, detailX, detailY, size, width, heigth, xz (horizontal plane) +*/ +Mesh.plane = function(options, gl) { + options = options || {}; + options.triangles = []; + var mesh = {}; + var detailX = options.detailX || options.detail || 1; + var detailY = options.detailY || options.detail || 1; + var width = options.width || options.size || 1; + var height = options.height || options.size || 1; + var xz = options.xz; + width *= 0.5; + height *= 0.5; + + var triangles = []; + var vertices = []; + var coords = []; + var normals = []; + + var N = vec3.fromValues(0,0,1); + if(xz) + N.set([0,1,0]); + + for (var y = 0; y <= detailY; y++) { + var t = y / detailY; + for (var x = 0; x <= detailX; x++) { + var s = x / detailX; + if(xz) + vertices.push((2 * s - 1) * width, 0, -(2 * t - 1) * height); + else + vertices.push((2 * s - 1) * width, (2 * t - 1) * height, 0); + coords.push(s, t); + normals.push(N[0],N[1],N[2]); + if (x < detailX && y < detailY) { + var i = x + y * (detailX + 1); + if(xz) //horizontal + { + triangles.push(i + 1, i + detailX + 1, i); + triangles.push(i + 1, i + detailX + 2, i + detailX + 1); + } + else //vertical + { + triangles.push(i, i + 1, i + detailX + 1); + triangles.push(i + detailX + 1, i + 1, i + detailX + 2); + } + } + } + } + + var bounding = BBox.fromCenterHalfsize( [0,0,0], xz ? [width,0,height] : [width,height,0] ); + var mesh_info = {vertices:vertices, normals: normals, coords: coords, triangles: triangles }; + return GL.Mesh.load( mesh_info, { bounding: bounding }, gl); +}; + +/** +* Returns a 2D Mesh (be careful, stream is vertices2D, used for 2D engines ) +* @method Mesh.plane2D +*/ +Mesh.plane2D = function(options, gl) { + var vertices = new Float32Array([-1,1, 1,-1, 1,1, -1,1, -1,-1, 1,-1]); + var coords = new Float32Array([0,1, 1,0, 1,1, 0,1, 0,0, 1,0]); + + if(options && options.size) + { + var s = options.size * 0.5; + for(var i = 0; i < vertices.length; ++i) + vertices[i] *= s; + } + return new GL.Mesh( {vertices2D: vertices, coords: coords },null,gl ); +}; + +/** +* Returns a point mesh +* @method Mesh.point +* @param {Object} options no options +*/ +Mesh.point = function(options) { + return new GL.Mesh( {vertices: [0,0,0]} ); +} + +/** +* Returns a cube mesh +* @method Mesh.cube +* @param {Object} options valid options: size +*/ +Mesh.cube = function(options, gl) { + options = options || {}; + var halfsize = (options.size || 1) * 0.5; + + var buffers = {}; + //[[-1,1,-1],[-1,-1,+1],[-1,1,1],[-1,1,-1],[-1,-1,-1],[-1,-1,+1],[1,1,-1],[1,1,1],[1,-1,+1],[1,1,-1],[1,-1,+1],[1,-1,-1],[-1,1,1],[1,-1,1],[1,1,1],[-1,1,1],[-1,-1,1],[1,-1,1],[-1,1,-1],[1,1,-1],[1,-1,-1],[-1,1,-1],[1,-1,-1],[-1,-1,-1],[-1,1,-1],[1,1,1],[1,1,-1],[-1,1,-1],[-1,1,1],[1,1,1],[-1,-1,-1],[1,-1,-1],[1,-1,1],[-1,-1,-1],[1,-1,1],[-1,-1,1]] + buffers.vertices = new Float32Array([-1,1,-1,-1,-1,+1, -1,1,1,-1,1,-1, -1,-1,-1,-1,-1,+1, 1,1,-1,1,1,1,1,-1,+1,1,1,-1,1,-1,+1,1,-1,-1,-1,1,1,1,-1,1,1,1,1,-1,1,1,-1,-1,1,1,-1,1,-1,1,-1,1,1,-1,1,-1,-1,-1,1,-1,1,-1,-1,-1,-1,-1,-1,1,-1,1,1,1,1,1,-1,-1,1,-1,-1,1,1,1,1,1,-1,-1,-1,1,-1,-1,1,-1,1,-1,-1,-1,1,-1,1,-1,-1,1]); + for(var i = 0, l = buffers.vertices.length; i < l; ++i) + buffers.vertices[i] *= halfsize; + + //[[-1,0,0],[-1,0,0],[-1,0,0],[-1,0,0],[-1,0,0],[-1,0,0],[1,0,0],[1,0,0],[1,0,0],[1,0,0],[1,0,0],[1,0,0],[0,0,1],[0,0,1],[0,0,1],[0,0,1],[0,0,1],[0,0,1],[0,0,-1],[0,0,-1],[0,0,-1],[0,0,-1],[0,0,-1],[0,0,-1],[0,1,0],[0,1,0],[0,1,0],[0,1,0],[0,1,0],[0,1,0],[0,-1,0],[0,-1,0],[0,-1,0],[0,-1,0],[0,-1,0],[0,-1,0]] + //[[0,1],[1,0],[1,1],[0,1],[0,0],[1,0],[1,1],[0,1],[0,0],[1,1],[0,0],[1,0],[0,1],[1,0],[1,1],[0,1],[0,0],[1,0],[1,1],[0,1],[0,0],[1,1],[0,0],[1,0],[0,1],[1,0],[1,1],[0,1],[0,0],[1,0],[1,1],[0,1],[0,0],[1,1],[0,0],[1,0]]; + buffers.normals = new Float32Array([-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0]); + buffers.coords = new Float32Array([0,1,1,0,1,1,0,1,0,0,1,0,1,1,0,1,0,0,1,1,0,0,1,0,0,1,1,0,1,1,0,1,0,0,1,0,1,1,0,1,0,0,1,1,0,0,1,0,0,1,1,0,1,1,0,1,0,0,1,0,1,1,0,1,0,0,1,1,0,0,1,0]); + + if(options.wireframe) + buffers.wireframe = new Uint16Array([0,2, 2,5, 5,4, 4,0, 6,7, 7,10, 10,11, 11,6, 0,6, 2,7, 5,10, 4,11 ]); + options.bounding = BBox.fromCenterHalfsize( [0,0,0], [halfsize,halfsize,halfsize] ); + return GL.Mesh.load(buffers, options, gl); +} + + +/** +* Returns a cube mesh of a given size +* @method Mesh.cube +* @param {Object} options valid options: size, sizex, sizey, sizez +*/ +Mesh.box = function(options, gl) { + options = options || {}; + var sizex = options.sizex || 1; + var sizey = options.sizey || 1; + var sizez = options.sizez || 1; + sizex *= 0.5; + sizey *= 0.5; + sizez *= 0.5; + + var buffers = {}; + //[[-1,1,-1],[-1,-1,+1],[-1,1,1],[-1,1,-1],[-1,-1,-1],[-1,-1,+1],[1,1,-1],[1,1,1],[1,-1,+1],[1,1,-1],[1,-1,+1],[1,-1,-1],[-1,1,1],[1,-1,1],[1,1,1],[-1,1,1],[-1,-1,1],[1,-1,1],[-1,1,-1],[1,1,-1],[1,-1,-1],[-1,1,-1],[1,-1,-1],[-1,-1,-1],[-1,1,-1],[1,1,1],[1,1,-1],[-1,1,-1],[-1,1,1],[1,1,1],[-1,-1,-1],[1,-1,-1],[1,-1,1],[-1,-1,-1],[1,-1,1],[-1,-1,1]] + buffers.vertices = new Float32Array([-1,1,-1,-1,-1,+1,-1,1,1,-1,1,-1,-1,-1,-1,-1,-1,+1,1,1,-1,1,1,1,1,-1,+1,1,1,-1,1,-1,+1,1,-1,-1,-1,1,1,1,-1,1,1,1,1,-1,1,1,-1,-1,1,1,-1,1,-1,1,-1,1,1,-1,1,-1,-1,-1,1,-1,1,-1,-1,-1,-1,-1,-1,1,-1,1,1,1,1,1,-1,-1,1,-1,-1,1,1,1,1,1,-1,-1,-1,1,-1,-1,1,-1,1,-1,-1,-1,1,-1,1,-1,-1,1]); + //for(var i in options.vertices) for(var j in options.vertices[i]) options.vertices[i][j] *= size; + for(var i = 0, l = buffers.vertices.length; i < l; i+=3) + { + buffers.vertices[i] *= sizex; + buffers.vertices[i+1] *= sizey; + buffers.vertices[i+2] *= sizez; + } + + //[[-1,0,0],[-1,0,0],[-1,0,0],[-1,0,0],[-1,0,0],[-1,0,0],[1,0,0],[1,0,0],[1,0,0],[1,0,0],[1,0,0],[1,0,0],[0,0,1],[0,0,1],[0,0,1],[0,0,1],[0,0,1],[0,0,1],[0,0,-1],[0,0,-1],[0,0,-1],[0,0,-1],[0,0,-1],[0,0,-1],[0,1,0],[0,1,0],[0,1,0],[0,1,0],[0,1,0],[0,1,0],[0,-1,0],[0,-1,0],[0,-1,0],[0,-1,0],[0,-1,0],[0,-1,0]] + //[[0,1],[1,0],[1,1],[0,1],[0,0],[1,0],[1,1],[0,1],[0,0],[1,1],[0,0],[1,0],[0,1],[1,0],[1,1],[0,1],[0,0],[1,0],[1,1],[0,1],[0,0],[1,1],[0,0],[1,0],[0,1],[1,0],[1,1],[0,1],[0,0],[1,0],[1,1],[0,1],[0,0],[1,1],[0,0],[1,0]]; + buffers.normals = new Float32Array([-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0]); + buffers.coords = new Float32Array([0,1,1,0,1,1,0,1,0,0,1,0,1,1,0,1,0,0,1,1,0,0,1,0,0,1,1,0,1,1,0,1,0,0,1,0,1,1,0,1,0,0,1,1,0,0,1,0,0,1,1,0,1,1,0,1,0,0,1,0,1,1,0,1,0,0,1,1,0,0,1,0]); + + if(options.wireframe) + buffers.wireframe = new Uint16Array([0,2, 2,5, 5,4, 4,0, 6,7, 7,10, 10,11, 11,6, 0,6, 2,7, 5,10, 4,11 ]); + + options.bounding = BBox.fromCenterHalfsize( [0,0,0], [sizex,sizey,sizez] ); + + return GL.Mesh.load(buffers, options, gl); +} + +/** +* Returns a circle mesh +* @method Mesh.circle +* @param {Object} options valid options: size,radius, xz = in xz plane, otherwise xy plane +*/ +Mesh.circle = function( options, gl ) { + options = options || {}; + var size = options.size || options.radius || 1; + var slices = Math.ceil(options.slices || 24); + var xz = options.xz || false; + var empty = options.empty || false; + if(slices < 3) slices = 3; + var delta = (2 * Math.PI) / slices; + + var center = vec3.create(); + var A = vec3.create(); + var N = vec3.fromValues(0,0,1); + var uv_center = vec2.fromValues(0.5,0.5); + var uv = vec2.create(); + + if(xz) N.set([0,1,0]); + + var index = xz ? 2 : 1; + + var vertices = new Float32Array(3 * (slices + 1)); + var normals = new Float32Array(3 * (slices + 1)); + var coords = new Float32Array(2 * (slices + 1)); + var triangles = null; + + //the center is always the same + vertices.set(center, 0); + normals.set(N, 0); + coords.set(uv_center, 0); + + var sin = 0; + var cos = 0; + + //compute vertices + for(var i = 0; i < slices; ++i ) + { + sin = Math.sin( delta * i ); + cos = Math.cos( delta * i ); + + A[0] = sin * size; + A[index] = cos * size; + uv[0] = sin * 0.5 + 0.5; + uv[1] = cos * 0.5 + 0.5; + vertices.set(A, i * 3 + 3); + normals.set(N, i * 3 + 3); + coords.set(uv, i * 2 + 2); + } + + if(empty) + { + vertices = vertices.subarray(3); + normals = vertices.subarray(3); + coords = vertices.subarray(2); + triangles = null; + } + else + { + var triangles = new Uint16Array(3 * slices); + var offset = 2; + var offset2 = 1; + if(xz) + { + offset = 1; + offset2 = 2; + } + + //compute indices + for(var i = 0; i < slices-1; ++i ) + { + triangles[i*3] = 0; + triangles[i*3+1] = i+offset; + triangles[i*3+2] = i+offset2; + } + + triangles[i*3] = 0; + if(xz) + { + triangles[i*3+1] = i+1; + triangles[i*3+2] = 1; + } + else + { + triangles[i*3+1] = 1; + triangles[i*3+2] = i+1; + } + } + + options.bounding = BBox.fromCenterHalfsize( [0,0,0], xz ? [size,0,size] : [size,size,0] ); + + var buffers = {vertices: vertices, normals: normals, coords: coords, triangles: triangles}; + + if(options.wireframe) + { + var wireframe = new Uint16Array(slices*2); + for(var i = 0; i < slices; i++) + { + wireframe[i*2] = i; + wireframe[i*2+1] = i+1; + } + wireframe[0] = slices; + buffers.wireframe = wireframe; + } + + return GL.Mesh.load( buffers, options, gl ); +} + +/** +* Returns a cube mesh +* @method Mesh.cylinder +* @param {Object} options valid options: radius, height, subdivisions +*/ +Mesh.cylinder = function( options, gl ) { + options = options || {}; + var radius = options.radius || options.size || 1; + var height = options.height || options.size || 2; + var subdivisions = options.subdivisions || 64; + + var vertices = new Float32Array(subdivisions * 6 * 3 * 2 ); + var normals = new Float32Array(subdivisions * 6 * 3 * 2 ); + var coords = new Float32Array(subdivisions * 6 * 2 * 2 ); + //not indexed because caps have different normals and uvs so... + + var delta = 2*Math.PI / subdivisions; + var normal = null; + for(var i = 0; i < subdivisions; ++i) + { + var angle = i * delta; + + normal = [ Math.sin(angle), 0, Math.cos(angle)]; + vertices.set([ normal[0]*radius, height*0.5, normal[2]*radius], i*6*3); + normals.set(normal, i*6*3 ); + coords.set([i/subdivisions,1], i*6*2 ); + + normal = [ Math.sin(angle), 0, Math.cos(angle)]; + vertices.set([ normal[0]*radius, height*-0.5, normal[2]*radius], i*6*3 + 3); + normals.set(normal, i*6*3 + 3); + coords.set([i/subdivisions,0], i*6*2 + 2); + + normal = [ Math.sin(angle+delta), 0, Math.cos(angle+delta)]; + vertices.set([ normal[0]*radius, height*-0.5, normal[2]*radius], i*6*3 + 6); + normals.set(normal, i*6*3 + 6); + coords.set([(i+1)/subdivisions,0], i*6*2 + 4); + + normal = [ Math.sin(angle+delta), 0, Math.cos(angle+delta)]; + vertices.set([ normal[0]*radius, height*0.5, normal[2]*radius], i*6*3 + 9); + normals.set(normal, i*6*3 + 9); + coords.set([(i+1)/subdivisions,1], i*6*2 + 6); + + normal = [ Math.sin(angle), 0, Math.cos(angle)]; + vertices.set([ normal[0]*radius, height*0.5, normal[2]*radius], i*6*3 + 12); + normals.set(normal, i*6*3 + 12); + coords.set([i/subdivisions,1], i*6*2 + 8); + + normal = [ Math.sin(angle+delta), 0, Math.cos(angle+delta)]; + vertices.set([ normal[0]*radius, height*-0.5, normal[2]*radius], i*6*3 + 15); + normals.set(normal, i*6*3 + 15); + coords.set([(i+1)/subdivisions,0], i*6*2 + 10); + } + + var pos = i*6*3; + var pos_uv = i*6*2; + var caps_start = pos; + + //caps + if( options.caps === false ) + { + //finalize arrays + vertices = vertices.subarray(0,pos); + normals = normals.subarray(0,pos); + coords = coords.subarray(0,pos_uv); + } + else + { + var top_center = vec3.fromValues(0,height*0.5,0); + var bottom_center = vec3.fromValues(0,height*-0.5,0); + var up = vec3.fromValues(0,1,0); + var down = vec3.fromValues(0,-1,0); + for(var i = 0; i < subdivisions; ++i) + { + var angle = i * delta; + + var uv = vec3.fromValues( Math.sin(angle), 0, Math.cos(angle) ); + var uv2 = vec3.fromValues( Math.sin(angle+delta), 0, Math.cos(angle+delta) ); + + vertices.set([ uv[0]*radius, height*0.5, uv[2]*radius], pos + i*6*3); + normals.set(up, pos + i*6*3 ); + coords.set( [ -uv[0] * 0.5 + 0.5,uv[2] * 0.5 + 0.5], pos_uv + i*6*2 ); + + vertices.set([ uv2[0]*radius, height*0.5, uv2[2]*radius], pos + i*6*3 + 3); + normals.set(up, pos + i*6*3 + 3 ); + coords.set( [ -uv2[0] * 0.5 + 0.5,uv2[2] * 0.5 + 0.5], pos_uv + i*6*2 + 2 ); + + vertices.set( top_center, pos + i*6*3 + 6 ); + normals.set(up, pos + i*6*3 + 6); + coords.set([0.5,0.5], pos_uv + i*6*2 + 4); + + //bottom + vertices.set([ uv2[0]*radius, height*-0.5, uv2[2]*radius], pos + i*6*3 + 9); + normals.set(down, pos + i*6*3 + 9); + coords.set( [ uv2[0] * 0.5 + 0.5,uv2[2] * 0.5 + 0.5], pos_uv + i*6*2 + 6); + + vertices.set([ uv[0]*radius, height*-0.5, uv[2]*radius], pos + i*6*3 + 12); + normals.set(down, pos + i*6*3 + 12 ); + coords.set( [ uv[0] * 0.5 + 0.5,uv[2] * 0.5 + 0.5], pos_uv + i*6*2 + 8 ); + + vertices.set( bottom_center, pos + i*6*3 + 15 ); + normals.set( down, pos + i*6*3 + 15); + coords.set( [0.5,0.5], pos_uv + i*6*2 + 10); + } + } + + var buffers = { + vertices: vertices, + normals: normals, + coords: coords + } + options.bounding = BBox.fromCenterHalfsize( [0,0,0], [radius,height*0.5,radius] ); + options.info = { groups: [] }; + + if(options.caps !== false) + { + options.info.groups.push({ name:"side", start: 0, length: caps_start / 3}); + options.info.groups.push({ name:"caps", start: caps_start / 3, length: (vertices.length - caps_start) / 3}); + } + + return Mesh.load( buffers, options, gl ); +} + +/** +* Returns a cone mesh +* @method Mesh.cone +* @param {Object} options valid options: radius, height, subdivisions +*/ +Mesh.cone = function( options, gl ) { + options = options || {}; + var radius = options.radius || options.size || 1; + var height = options.height || options.size || 2; + var subdivisions = options.subdivisions || 64; + + var vertices = new Float32Array(subdivisions * 3 * 3 * 2); + var normals = new Float32Array(subdivisions * 3 * 3 * 2); + var coords = new Float32Array(subdivisions * 2 * 3 * 2); + //not indexed because caps have different normals and uvs so... + + var delta = 2*Math.PI / subdivisions; + var normal = null; + var normal_y = radius / height; + var up = [0,1,0]; + + for(var i = 0; i < subdivisions; ++i) + { + var angle = i * delta; + + normal = [ Math.sin(angle+delta*0.5), normal_y, Math.cos(angle+delta*0.5)]; + vec3.normalize(normal,normal); + //normal = up; + vertices.set([ 0, height, 0] , i*6*3); + normals.set(normal, i*6*3 ); + coords.set([i/subdivisions,1], i*6*2 ); + + normal = [ Math.sin(angle), normal_y, Math.cos(angle)]; + vertices.set([ normal[0]*radius, 0, normal[2]*radius], i*6*3 + 3); + vec3.normalize(normal,normal); + normals.set(normal, i*6*3 + 3); + coords.set([i/subdivisions,0], i*6*2 + 2); + + normal = [ Math.sin(angle+delta), normal_y, Math.cos(angle+delta)]; + vertices.set([ normal[0]*radius, 0, normal[2]*radius], i*6*3 + 6); + vec3.normalize(normal,normal); + normals.set(normal, i*6*3 + 6); + coords.set([(i+1)/subdivisions,0], i*6*2 + 4); + } + + var pos = 0;//i*3*3; + var pos_uv = 0;//i*3*2; + + //cap + var bottom_center = vec3.fromValues(0,0,0); + var down = vec3.fromValues(0,-1,0); + for(var i = 0; i < subdivisions; ++i) + { + var angle = i * delta; + + var uv = vec3.fromValues( Math.sin(angle), 0, Math.cos(angle) ); + var uv2 = vec3.fromValues( Math.sin(angle+delta), 0, Math.cos(angle+delta) ); + + //bottom + vertices.set([ uv2[0]*radius, 0, uv2[2]*radius], pos + i*6*3 + 9); + normals.set(down, pos + i*6*3 + 9); + coords.set( [ uv2[0] * 0.5 + 0.5,uv2[2] * 0.5 + 0.5], pos_uv + i*6*2 + 6); + + vertices.set([ uv[0]*radius, 0, uv[2]*radius], pos + i*6*3 + 12); + normals.set(down, pos + i*6*3 + 12 ); + coords.set( [ uv[0] * 0.5 + 0.5,uv[2] * 0.5 + 0.5], pos_uv + i*6*2 + 8 ); + + vertices.set( bottom_center, pos + i*6*3 + 15 ); + normals.set( down, pos + i*6*3 + 15); + coords.set( [0.5,0.5], pos_uv + i*6*2 + 10); + } + + var buffers = { + vertices: vertices, + normals: normals, + coords: coords + } + options.bounding = BBox.fromCenterHalfsize( [0,height*0.5,0], [radius,height*0.5,radius] ); + + return Mesh.load( buffers, options, gl ); +} + +/** +* Returns a sphere mesh +* @method Mesh.sphere +* @param {Object} options valid options: radius, lat, long, subdivisions, hemi +*/ +Mesh.sphere = function( options, gl ) { + options = options || {}; + var radius = options.radius || options.size || 1; + var latitudeBands = options.lat || options.subdivisions || 16; + var longitudeBands = options["long"] || options.subdivisions || 16; + + var vertexPositionData = new Float32Array( (latitudeBands+1)*(longitudeBands+1)*3 ); + var normalData = new Float32Array( (latitudeBands+1)*(longitudeBands+1)*3 ); + var textureCoordData = new Float32Array( (latitudeBands+1)*(longitudeBands+1)*2 ); + var indexData = new Uint16Array( latitudeBands*longitudeBands*6 ); + var latRange = options.hemi ? Math.PI * 0.5 : Math.PI; + + var i = 0, iuv = 0; + for (var latNumber = 0; latNumber <= latitudeBands; latNumber++) + { + var theta = latNumber * latRange / latitudeBands; + var sinTheta = Math.sin(theta); + var cosTheta = Math.cos(theta); + + for (var longNumber = 0; longNumber <= longitudeBands; longNumber++) + { + var phi = longNumber * 2 * Math.PI / longitudeBands; + var sinPhi = Math.sin(phi); + var cosPhi = Math.cos(phi); + + var x = cosPhi * sinTheta; + var y = cosTheta; + var z = sinPhi * sinTheta; + var u = 1- (longNumber / longitudeBands); + var v = (1 - latNumber / latitudeBands); + + vertexPositionData.set([radius * x,radius * y,radius * z],i); + normalData.set([x,y,z],i); + textureCoordData.set([u,v], iuv ); + i += 3; + iuv += 2; + } + } + + i=0; + for (var latNumber = 0; latNumber < latitudeBands; latNumber++) + { + for (var longNumber = 0; longNumber < longitudeBands; longNumber++) + { + var first = (latNumber * (longitudeBands + 1)) + longNumber; + var second = first + longitudeBands + 1; + + indexData.set([second,first,first + 1], i); + indexData.set([second + 1,second,first + 1], i+3); + i += 6; + } + } + + var buffers = { + vertices: vertexPositionData, + normals: normalData, + coords: textureCoordData, + triangles: indexData + }; + + if(options.wireframe) + { + var wireframe = new Uint16Array(longitudeBands*latitudeBands*4); + var pos = 0; + for(var i = 0; i < latitudeBands; i++) + { + for(var j = 0; j < longitudeBands; j++) + { + wireframe[pos] = i*(longitudeBands+1) + j; + wireframe[pos + 1] = i*(longitudeBands+1) + j + 1; + pos += 2; + } + wireframe[pos - longitudeBands*2] = i*(longitudeBands+1) + j; + } + + for(var i = 0; i < longitudeBands; i++) + for(var j = 0; j < latitudeBands; j++) + { + wireframe[pos] = j*(longitudeBands+1) + i; + wireframe[pos + 1] = (j+1)*(longitudeBands+1) + i; + pos += 2; + } + buffers.wireframe = wireframe; + } + + if(options.hemi) + options.bounding = BBox.fromCenterHalfsize( [0,radius*0.5,0], [radius,radius*0.5,radius], radius ); + else + options.bounding = BBox.fromCenterHalfsize( [0,0,0], [radius,radius,radius], radius ); + return GL.Mesh.load( buffers, options, gl ); +} + +/** +* Returns a grid mesh (must be rendered using gl.LINES) +* @method Mesh.grid +* @param {Object} options valid options: size, lines +*/ +Mesh.grid = function( options, gl ) +{ + options = options || {}; + var num_lines = options.lines || 11; + if(num_lines < 0) + num_lines = 1; + var size = options.size || 10; + + var vertexPositionData = new Float32Array( num_lines*2*2*3 ); + var hsize = size * 0.5; + var pos = 0; + var x = -hsize; + var delta = size / (num_lines-1); + + for(var i = 0; i < num_lines; i++) + { + vertexPositionData[ pos ] = x; + vertexPositionData[ pos+2 ] = -hsize; + vertexPositionData[ pos+3 ] = x; + vertexPositionData[ pos+5 ] = hsize; + + vertexPositionData[ pos+6 ] = hsize; + vertexPositionData[ pos+8 ] = x + vertexPositionData[ pos+9 ] = -hsize; + vertexPositionData[ pos+11 ] = x + + x += delta; + pos += 12; + } + + return new GL.Mesh({vertices: vertexPositionData}, options, gl ); +} + + +/** +* Returns a icosahedron mesh (useful to create spheres by subdivision) +* @method Mesh.icosahedron +* @param {Object} options valid options: radius, subdivisions (max: 6) +*/ +Mesh.icosahedron = function( options, gl ) { + options = options || {}; + var radius = options.radius || options.size || 1; + var subdivisions = options.subdivisions === undefined ? 0 : options.subdivisions; + if(subdivisions > 6) //dangerous + subdivisions = 6; + + var t = (1.0 + Math.sqrt(5)) / 2.0; + var vertices = [-1,t,0, 1,t,0, -1,-t,0, 1,-t,0, + 0,-1,t, 0,1,t, 0,-1,-t, 0,1,-t, + t,0,-1, t,0,1, -t,0,-1, -t,0,1]; + var normals = []; + var coords = []; + var indices = [0,11,5, 0,5,1, 0,1,7, 0,7,10, 0,10,11, 1,5,9, 5,11,4, 11,10,2, 10,7,6, 7,1,8, 3,9,4, 3,4,2, 3,2,6, 3,6,8, 3,8,9, 4,9,5, 2,4,11, 6,2,10, 8,6,7, 9,8,1 ]; + + //normalize + var l = vertices.length; + for(var i = 0; i < l; i+=3) + { + var mod = Math.sqrt( vertices[i]*vertices[i] + vertices[i+1]*vertices[i+1] + vertices[i+2]*vertices[i+2] ); + var normalx = vertices[i] / mod; + var normaly = vertices[i+1] / mod; + var normalz = vertices[i+2] / mod; + normals.push( normalx, normaly, normalz ); + coords.push( Math.atan2( normalx, normalz ), Math.acos( normaly ) ); + vertices[i] *= radius/mod; + vertices[i+1] *= radius/mod; + vertices[i+2] *= radius/mod; + } + + var middles = {}; + + //A,B = index of vertex in vertex array + function middlePoint( A, B ) + { + var key = indices[A] < indices[B] ? indices[A] + ":"+indices[B] : indices[B]+":"+indices[A]; + var r = middles[key]; + if(r) + return r; + var index = vertices.length / 3; + vertices.push(( vertices[ indices[A]*3] + vertices[ indices[B]*3 ]) * 0.5, + (vertices[ indices[A]*3+1] + vertices[ indices[B]*3+1 ]) * 0.5, + (vertices[ indices[A]*3+2] + vertices[ indices[B]*3+2 ]) * 0.5); + + var mod = Math.sqrt( vertices[index*3]*vertices[index*3] + vertices[index*3+1]*vertices[index*3+1] + vertices[index*3+2]*vertices[index*3+2] ); + var normalx = vertices[index*3] / mod; + var normaly = vertices[index*3+1] / mod; + var normalz = vertices[index*3+2] / mod; + normals.push( normalx, normaly, normalz ); + coords.push( (Math.atan2( normalx, normalz ) / Math.PI) * 0.5, (Math.acos( normaly ) / Math.PI) ); + vertices[index*3] *= radius/mod; + vertices[index*3+1] *= radius/mod; + vertices[index*3+2] *= radius/mod; + + middles[key] = index; + return index; + } + + for (var iR = 0; iR < subdivisions; ++iR ) + { + var new_indices = []; + var l = indices.length; + for(var i = 0; i < l; i+=3) + { + var MA = middlePoint( i, i+1 ); + var MB = middlePoint( i+1, i+2); + var MC = middlePoint( i+2, i); + new_indices.push(indices[i], MA, MC); + new_indices.push(indices[i+1], MB, MA); + new_indices.push(indices[i+2], MC, MB); + new_indices.push(MA, MB, MC); + } + indices = new_indices; + } + + options.bounding = BBox.fromCenterHalfsize( [0,0,0], [radius,radius,radius], radius ); + + return new GL.Mesh.load({vertices: vertices, coords: coords, normals: normals, triangles: indices},options,gl); +} +/** +* @namespace GL +*/ + +/** +* Texture class to upload images to the GPU, default is gl.TEXTURE_2D, gl.RGBA of gl.UNSIGNED_BYTE with filters set to gl.LINEAR and wrap to gl.CLAMP_TO_EDGE
+ There is a list of options
+ ==========================
+ - texture_type: gl.TEXTURE_2D, gl.TEXTURE_CUBE_MAP, default gl.TEXTURE_2D
+ - format: gl.RGB, gl.RGBA, gl.DEPTH_COMPONENT, default gl.RGBA
+ - type: gl.UNSIGNED_BYTE, gl.UNSIGNED_SHORT, gl.HALF_FLOAT_OES, gl.FLOAT, default gl.UNSIGNED_BYTE
+ - filter: filtering for mag and min: gl.NEAREST or gl.LINEAR, default gl.NEAREST
+ - magFilter: magnifying filter: gl.NEAREST, gl.LINEAR, default gl.NEAREST
+ - minFilter: minifying filter: gl.NEAREST, gl.LINEAR, gl.LINEAR_MIPMAP_LINEAR, default gl.NEAREST
+ - wrap: texture wrapping: gl.CLAMP_TO_EDGE, gl.REPEAT, gl.MIRROR, default gl.CLAMP_TO_EDGE (also accepts wrapT and wrapS for separate settings)
+ - pixel_data: ArrayBufferView with the pixel data to upload to the texture, otherwise the texture will be black (if cubemaps then pass an array[6] with the data for every face)
+ - premultiply_alpha : multiply the color by the alpha value when uploading, default FALSE
+ - no_flip : do not flip in Y, default TRUE
+ - anisotropic : number of anisotropic fetches, default 0
+ + check for more info about formats: https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/texImage2D + +* @class Texture +* @param {number} width texture width (any supported but Power of Two allows to have mipmaps), 0 means no memory reserved till its filled +* @param {number} height texture height (any supported but Power of Two allows to have mipmaps), 0 means no memory reserved till its filled +* @param {Object} options Check the list in the description +* @constructor +*/ + +global.Texture = GL.Texture = function Texture( width, height, options, gl ) { + options = options || {}; + + //used to avoid problems with resources moving between different webgl context + gl = gl || global.gl; + this.gl = gl; + this._context_id = gl.context_id; + + //round sizes + width = parseInt(width); + height = parseInt(height); + + if(GL.debug) + console.log("GL.Texture created: ",width,height); + + //create texture handler + this.handler = gl.createTexture(); + + //set settings + this.width = width; + this.height = height; + if(options.depth) //for texture_3d + this.depth = options.depth; + this.texture_type = options.texture_type || gl.TEXTURE_2D; //or gl.TEXTURE_CUBE_MAP + this.format = options.format || Texture.DEFAULT_FORMAT; //gl.RGBA (if gl.DEPTH_COMPONENT remember type: gl.UNSIGNED_SHORT) + this.internalFormat = options.internalFormat; //LUMINANCE, and weird formats with bits + this.type = options.type || Texture.DEFAULT_TYPE; //gl.UNSIGNED_BYTE, gl.UNSIGNED_SHORT, gl.FLOAT or gl.HALF_FLOAT_OES (or gl.HIGH_PRECISION_FORMAT which could be half or float) + this.magFilter = options.magFilter || options.filter || Texture.DEFAULT_MAG_FILTER; + this.minFilter = options.minFilter || options.filter || Texture.DEFAULT_MIN_FILTER; + this.wrapS = options.wrap || options.wrapS || Texture.DEFAULT_WRAP_S; + this.wrapT = options.wrap || options.wrapT || Texture.DEFAULT_WRAP_T; + this.data = null; //where the data came from + + //precompute the max amount of texture units + if(!Texture.MAX_TEXTURE_IMAGE_UNITS) + Texture.MAX_TEXTURE_IMAGE_UNITS = gl.getParameter( gl.MAX_TEXTURE_IMAGE_UNITS ); + + this.has_mipmaps = false; + + if( this.format == gl.DEPTH_COMPONENT && gl.webgl_version == 1 && !gl.extensions["WEBGL_depth_texture"] ) + throw("Depth Texture not supported"); + if( this.type == gl.FLOAT && !gl.extensions["OES_texture_float"] && gl.webgl_version == 1 ) + throw("Float Texture not supported"); + if( this.type == gl.HALF_FLOAT_OES) + { + if( !gl.extensions["OES_texture_half_float"] && gl.webgl_version == 1 ) + throw("Half Float Texture extension not supported."); + else if( gl.webgl_version > 1 ) + { + console.warn("using HALF_FLOAT_OES in WebGL2 is deprecated, suing HALF_FLOAT instead"); + this.type = this.format == gl.RGB ? gl.RGB16F : gl.RGBA16F; + } + } + if( (!isPowerOfTwo(this.width) || !isPowerOfTwo(this.height)) && //non power of two + ( (this.minFilter != gl.NEAREST && this.minFilter != gl.LINEAR) || //uses mipmaps + (this.wrapS != gl.CLAMP_TO_EDGE || this.wrapT != gl.CLAMP_TO_EDGE) ) ) //uses wrap + { + if(!options.ignore_pot) + throw("Cannot use texture-wrap or mipmaps in Non-Power-of-Two textures"); + else + { + this.minFilter = this.magFilter = gl.LINEAR; + this.wrapS = this.wrapT = gl.CLAMP_TO_EDGE; + } + } + + //empty textures are allowed to be created + if(!width || !height) + return; + + //because sometimes the internal format is not so obvious + if(!this.internalFormat) + this.computeInternalFormat(); + + //this is done because in some cases the user binds a texture to slot 0 and then creates a new one, which overrides slot 0 + gl.activeTexture( gl.TEXTURE0 + Texture.MAX_TEXTURE_IMAGE_UNITS - 1); + //I use an invalid gl enum to say this texture is a depth texture, ugly, I know... + gl.bindTexture( this.texture_type, this.handler); + gl.texParameteri( this.texture_type, gl.TEXTURE_MAG_FILTER, this.magFilter ); + gl.texParameteri( this.texture_type, gl.TEXTURE_MIN_FILTER, this.minFilter ); + gl.texParameteri( this.texture_type, gl.TEXTURE_WRAP_S, this.wrapS ); + gl.texParameteri( this.texture_type, gl.TEXTURE_WRAP_T, this.wrapT ); + + if(options.anisotropic && gl.extensions["EXT_texture_filter_anisotropic"]) + gl.texParameterf( GL.TEXTURE_2D, gl.extensions["EXT_texture_filter_anisotropic"].TEXTURE_MAX_ANISOTROPY_EXT, options.anisotropic); + + var type = this.type; + var pixel_data = options.pixel_data; + if(pixel_data && !pixel_data.buffer) + { + if( this.texture_type == GL.TEXTURE_CUBE_MAP ) + { + if(pixel_data[0].constructor === Number) //special case, specify just one face and copy it + { + pixel_data = toTypedArray( pixel_data ); + pixel_data = [pixel_data,pixel_data,pixel_data,pixel_data,pixel_data,pixel_data]; + } + else + for(var i = 0; i < pixel_data.length; ++i) + pixel_data[i] = toTypedArray( pixel_data[i] ); + } + else + pixel_data = toTypedArray( pixel_data ); + this.data = pixel_data; + } + + function toTypedArray( data ) + { + if(data.constructor !== Array) + return data; + if( type == GL.FLOAT) + return new Float32Array( data ); + if( type == GL.HALF_FLOAT_OES) + return new Uint16Array( data ); + return new Uint8Array( data ); + } + + //gl.TEXTURE_1D is not supported by WebGL... + + //here we create all ********************************** + if(this.texture_type == GL.TEXTURE_2D) + { + //create the texture + gl.texImage2D( GL.TEXTURE_2D, 0, this.internalFormat, width, height, 0, this.format, this.type, pixel_data || null ); + + //generate empty mipmaps (necessary?) + if ( GL.isPowerOfTwo(width) && GL.isPowerOfTwo(height) && options.minFilter && options.minFilter != gl.NEAREST && options.minFilter != gl.LINEAR) + { + gl.generateMipmap( this.texture_type ); + this.has_mipmaps = true; + } + } + else if(this.texture_type == GL.TEXTURE_CUBE_MAP) + { + for(var i = 0; i < 6; ++i) + gl.texImage2D( gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, this.internalFormat, this.width, this.height, 0, this.format, this.type, pixel_data ? pixel_data[i] : null ); + } + else if(this.texture_type == GL.TEXTURE_3D) + { + if(this.gl.webgl_version == 1) + throw("TEXTURE_3D not supported in WebGL 1. Enable WebGL 2 in the context by passing webgl2:true to the context"); + if(!options.depth) + throw("3d texture depth must be set in the options.depth"); + gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false ); //standard does not allow this flags for 3D textures + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false ); + gl.texImage3D( GL.TEXTURE_3D, 0, this.internalFormat, width, height, options.depth, 0, this.format, this.type, pixel_data || null ); + } + gl.bindTexture(this.texture_type, null); //disable + gl.activeTexture(gl.TEXTURE0); +} + +Texture.DEFAULT_TYPE = GL.UNSIGNED_BYTE; +Texture.DEFAULT_FORMAT = GL.RGBA; +Texture.DEFAULT_MAG_FILTER = GL.LINEAR; +Texture.DEFAULT_MIN_FILTER = GL.LINEAR; +Texture.DEFAULT_WRAP_S = GL.CLAMP_TO_EDGE; +Texture.DEFAULT_WRAP_T = GL.CLAMP_TO_EDGE; +Texture.EXTENSION = "png"; //used when saving it to file + +//used for render to FBOs +Texture.framebuffer = null; +Texture.renderbuffer = null; +Texture.loading_color = new Uint8Array([0,0,0,0]); +Texture.use_renderbuffer_pool = true; //should improve performance + +//because usually you dont want to specify the internalFormat, this tries to guess it from its format +//check https://webgl2fundamentals.org/webgl/lessons/webgl-data-textures.html for more info +Texture.prototype.computeInternalFormat = function() +{ + this.internalFormat = this.format; //default + + //automatic selection of internal format for depth textures to avoid problems between webgl1 and 2 + if( this.format == GL.DEPTH_COMPONENT ) + { + this.minFilter = this.magFilter = GL.NEAREST; + + if( gl.webgl_version == 2 ) + { + if( this.type == GL.UNSIGNED_SHORT ) + this.internalFormat = GL.DEPTH_COMPONENT16; + else if( this.type == GL.UNSIGNED_INT ) + this.internalFormat = GL.DEPTH_COMPONENT24; + else if( this.type == GL.FLOAT ) + this.internalFormat = GL.DEPTH_COMPONENT32F; + else + throw("unsupported type for a depth texture"); + } + else if( gl.webgl_version == 1 ) + { + if( this.type == GL.FLOAT ) + throw("WebGL 1.0 does not support float depth textures"); + this.internalFormat = GL.DEPTH_COMPONENT; + } + } + else if( this.format == gl.RGBA ) + { + if( gl.webgl_version == 2 ) + { + if( this.type == GL.FLOAT ) + this.internalFormat = GL.RGBA32F; + else if( this.type == GL.HALF_FLOAT ) + this.internalFormat = GL.RGBA16F; + else if( this.type == GL.HALF_FLOAT_OES ) + { + console.warn("webgl 2 does not use HALF_FLOAT_OES, converting to HALF_FLOAT") + this.type = GL.HALF_FLOAT; + this.internalFormat = GL.RGBA16F; + } + /* + else if( this.type == GL.UNSIGNED_SHORT ) + { + this.internalFormat = GL.RGBA16UI; + this.format = gl.RGBA_INTEGER; + } + else if( this.type == GL.UNSIGNED_INT ) + { + this.internalFormat = GL.RGBA32UI; + this.format = gl.RGBA_INTEGER; + } + */ + } + else if( gl.webgl_version == 1 ) + { + if( this.type == GL.HALF_FLOAT ) + { + console.warn("webgl 1 does not use HALF_FLOAT, converting to HALF_FLOAT_OES") + this.type = GL.HALF_FLOAT_OES; + } + } + } +} + +/** +* Free the texture memory from the GPU, sets the texture handler to null +* @method delete +*/ +Texture.prototype.delete = function() +{ + gl.deleteTexture( this.handler ); + this.handler = null; +} + +Texture.prototype.getProperties = function() +{ + return { + width: this.width, + height: this.height, + type: this.type, + format: this.format, + texture_type: this.texture_type, + magFilter: this.magFilter, + minFilter: this.minFilter, + wrapS: this.wrapS, + wrapT: this.wrapT + }; +} + +Texture.prototype.hasSameProperties = function(t) +{ + if(!t) + return false; + return t.width == this.width && + t.height == this.height && + t.type == this.type && + t.format == this.format && + t.texture_type == this.texture_type; +} + +Texture.prototype.hasSameSize = function(t) +{ + if(!t) + return false; + return t.width == this.width && t.height == this.height; +} +//textures cannot be stored in JSON +Texture.prototype.toJSON = function() +{ + return ""; +} + + +/** +* Returns if depth texture is supported by the GPU +* @method isDepthSupported +* @return {Boolean} true if supported +*/ +Texture.isDepthSupported = function() +{ + return gl.extensions["WEBGL_depth_texture"] != null; +} + +/** +* Binds the texture to one texture unit +* @method bind +* @param {number} unit texture unit +* @return {number} returns the texture unit +*/ +Texture.prototype.bind = function( unit ) { + if(unit == undefined) + unit = 0; + var gl = this.gl; + + //TODO: if the texture is not uploaded, must be upload now + + //bind + gl.activeTexture(gl.TEXTURE0 + unit); + gl.bindTexture( this.texture_type, this.handler ); + return unit; +} + +/** +* Unbinds the texture +* @method unbind +* @param {number} unit texture unit +* @return {number} returns the texture unit +*/ +Texture.prototype.unbind = function(unit) { + if(unit === undefined) + unit = 0; + var gl = this.gl; + gl.activeTexture(gl.TEXTURE0 + unit ); + gl.bindTexture(this.texture_type, null); +} + + +Texture.prototype.setParameter = function(param,value) { + this.bind(0); + this.gl.texParameteri( this.texture_type, param, value ); + switch(param) + { + case this.gl.TEXTURE_MAG_FILTER: this.magFilter = value; break; + case this.gl.TEXTURE_MIN_FILTER: this.minFilter = value; break; + case this.gl.TEXTURE_WRAP_S: this.wrapS = value; break; + case this.gl.TEXTURE_WRAP_T: this.wrapT = value; break; + } +} + +/** +* Unbinds the texture +* @method Texture.setUploadOptions +* @param {Object} options a list of options to upload the texture +* - premultiply_alpha : multiply the color by the alpha value, default FALSE +* - no_flip : do not flip in Y, default TRUE +*/ +Texture.setUploadOptions = function(options, gl) +{ + gl = gl || global.gl; + + if(options) //options that are not stored in the texture should be passed again to avoid reusing unknown state + { + gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, !!(options.premultiply_alpha) ); + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, !(options.no_flip) ); + } + else + { + gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false ); + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true ); + } + gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1); +} + +/** +* Given an Image/Canvas/Video it uploads it to the GPU +* @method uploadImage +* @param {Image} img +* @param {Object} options [optional] upload options (premultiply_alpha, no_flip) +*/ +Texture.prototype.uploadImage = function( image, options ) +{ + this.bind(); + var gl = this.gl; + if(!image) + throw("uploadImage parameter must be Image"); + + Texture.setUploadOptions(options, gl); + + try { + gl.texImage2D( gl.TEXTURE_2D, 0, this.format, this.format, this.type, image ); + this.width = image.videoWidth || image.width; + this.height = image.videoHeight || image.height; + this.data = image; + } catch (e) { + if (location.protocol == 'file:') { + throw 'image not loaded for security reasons (serve this page over "http://" instead)'; + } else { + throw 'image not loaded for security reasons (image must originate from the same ' + + 'domain as this page or use Cross-Origin Resource Sharing)'; + } + } + + //TODO: add expand transparent pixels option + + //generate mipmaps + if (this.minFilter && this.minFilter != gl.NEAREST && this.minFilter != gl.LINEAR) { + gl.generateMipmap(this.texture_type); + this.has_mipmaps = true; + } + gl.bindTexture(this.texture_type, null); //disable +} + +/** +* Uploads data to the GPU (data must have the appropiate size) +* @method uploadData +* @param {ArrayBuffer} data +* @param {Object} options [optional] upload options (premultiply_alpha, no_flip, cubemap_face, mipmap_level) +*/ +Texture.prototype.uploadData = function( data, options, skip_mipmaps ) +{ + options = options || {}; + if(!data) + throw("no data passed"); + var gl = this.gl; + this.bind(); + Texture.setUploadOptions(options, gl); + var mipmap_level = options.mipmap_level || 0; + var width = this.width; + var height = this.height; + width = width >> mipmap_level; + height = height >> mipmap_level; + var internal_format = this.internalFormat || this.format; + + if( this.type == GL.HALF_FLOAT_OES && data.constructor === Float32Array ) + console.warn("cannot uploadData to a HALF_FLOAT texture from a Float32Array, must be Uint16Array. To upload it we recomment to create a FLOAT texture, upload data there and copy to your HALF_FLOAT."); + + if( this.texture_type == GL.TEXTURE_2D ) + { + if(gl.webgl_version == 1) + { + if(data.buffer && data.buffer.constructor == ArrayBuffer) + gl.texImage2D(this.texture_type, mipmap_level, internal_format, width, height, 0, this.format, this.type, data); + else + gl.texImage2D(this.texture_type, mipmap_level, internal_format, this.format, this.type, data); + } + else if(gl.webgl_version == 2) //webgl forces to use width and height + { + if(data.buffer && data.buffer.constructor == ArrayBuffer) + gl.texImage2D(this.texture_type, mipmap_level, internal_format, width, height, 0, this.format, this.type, data); + else + gl.texImage2D(this.texture_type, mipmap_level, internal_format, width, height, 0, this.format, this.type, data); + } + } + else if( this.texture_type == GL.TEXTURE_3D ) + { + gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false ); //standard does not allow this flags for 3D textures + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false ); + gl.texImage3D( this.texture_type, mipmap_level, internal_format, width, height, this.depth >> mipmap_level, 0, this.format, this.type, data); + } + else if( this.texture_type == GL.TEXTURE_CUBE_MAP ) + gl.texImage2D( gl.TEXTURE_CUBE_MAP_POSITIVE_X + (options.cubemap_face || 0), mipmap_level, internal_format, width, height, 0, this.format, this.type, data); + else + throw("cannot uploadData for this texture type"); + + this.data = data; //should I clone it? + + if (!skip_mipmaps && this.minFilter && this.minFilter != gl.NEAREST && this.minFilter != gl.LINEAR) { + gl.generateMipmap(this.texture_type); + this.has_mipmaps = true; + } + gl.bindTexture(this.texture_type, null); //disable +} + +//When creating cubemaps this is helpful + +/*THIS WORKS old +Texture.cubemap_camera_parameters = [ + { type:"posX", dir: vec3.fromValues(-1,0,0), up: vec3.fromValues(0,1,0), right: vec3.fromValues(0,0,-1) }, + { type:"negX", dir: vec3.fromValues(1,0,0), up: vec3.fromValues(0,1,0), right: vec3.fromValues(0,0,1) }, + { type:"posY", dir: vec3.fromValues(0,-1,0), up: vec3.fromValues(0,0,-1), right: vec3.fromValues(1,0,0) }, + { type:"negY", dir: vec3.fromValues(0,1,0), up: vec3.fromValues(0,0,1), right: vec3.fromValues(-1,0,0) }, + { type:"posZ", dir: vec3.fromValues(0,0,-1), up: vec3.fromValues(0,1,0), right: vec3.fromValues(1,0,0) }, + { type:"negZ", dir: vec3.fromValues(0,0,1), up: vec3.fromValues(0,1,0), right: vec3.fromValues(-1,0,0) } +]; +*/ + +//THIS works +Texture.cubemap_camera_parameters = [ + { type:"posX", dir: vec3.fromValues(1,0,0), up: vec3.fromValues(0,1,0), right: vec3.fromValues(0,0,-1) }, + { type:"negX", dir: vec3.fromValues(-1,0,0), up: vec3.fromValues(0,1,0), right: vec3.fromValues(0,0,1) }, + { type:"posY", dir: vec3.fromValues(0,1,0), up: vec3.fromValues(0,0,-1), right: vec3.fromValues(1,0,0) }, + { type:"negY", dir: vec3.fromValues(0,-1,0), up: vec3.fromValues(0,0,1), right: vec3.fromValues(1,0,0) }, + { type:"posZ", dir: vec3.fromValues(0,0,1), up: vec3.fromValues(0,1,0), right: vec3.fromValues(1,0,0) }, + { type:"negZ", dir: vec3.fromValues(0,0,-1), up: vec3.fromValues(0,1,0), right: vec3.fromValues(-1,0,0) } +]; + + + +/** +* Render to texture using FBO, just pass the callback to a rendering function and the content of the texture will be updated +* If the texture is a cubemap, the callback will be called six times, once per face, the number of the face is passed as a second parameter +* for further info about how to set up the propper cubemap camera, check the GL.Texture.cubemap_camera_parameters with the direction and up vector for every face. +* +* Keep in mind that it tries to reuse the last renderbuffer for the depth, and if it cannot (different size) it creates a new one (throwing the old) +* @method drawTo +* @param {Function} callback function that does all the rendering inside this texture +*/ +Texture.prototype.drawTo = function(callback, params) +{ + var gl = this.gl; + + //if(this.format == gl.DEPTH_COMPONENT) + // throw("cannot use drawTo in depth textures, use Texture.drawToColorAndDepth"); + + var v = gl.getViewport(); + var now = GL.getTime(); + + var old_fbo = gl.getParameter( gl.FRAMEBUFFER_BINDING ); + + var framebuffer = gl._framebuffer = gl._framebuffer || gl.createFramebuffer(); + gl.bindFramebuffer( gl.FRAMEBUFFER, framebuffer ); + + //this code allows to reuse old renderbuffers instead of creating and destroying them for every frame + var renderbuffer = null; + + if( Texture.use_renderbuffer_pool ) //create a renderbuffer pool + { + if(!gl._renderbuffers_pool) + gl._renderbuffers_pool = {}; + //generate unique key for this renderbuffer + var key = this.width + ":" + this.height; + + //reuse or create new one + if( gl._renderbuffers_pool[ key ] ) //Reuse old + { + renderbuffer = gl._renderbuffers_pool[ key ]; + renderbuffer.time = now; + gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuffer ); + } + else + { + //create temporary buffer + gl._renderbuffers_pool[ key ] = renderbuffer = gl.createRenderbuffer(); + renderbuffer.time = now; + renderbuffer.width = this.width; + renderbuffer.height = this.height; + gl.bindRenderbuffer( gl.RENDERBUFFER, renderbuffer ); + + //destroy after one minute + setTimeout( inner_check_destroy.bind(renderbuffer), 1000*60 ); + } + } + else + { + renderbuffer = gl._renderbuffer = gl._renderbuffer || gl.createRenderbuffer(); + renderbuffer.width = this.width; + renderbuffer.height = this.height; + gl.bindRenderbuffer( gl.RENDERBUFFER, renderbuffer ); + } + + + //bind render buffer for depth or color + if( this.format === gl.DEPTH_COMPONENT ) + gl.renderbufferStorage( gl.RENDERBUFFER, gl.RGBA4, this.width, this.height); + else + gl.renderbufferStorage( gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, this.width, this.height); + + + //clears memory from unused buffer + function inner_check_destroy() + { + if( GL.getTime() - this.time >= 1000*60 ) + { + //console.log("Buffer cleared"); + gl.deleteRenderbuffer( gl._renderbuffers_pool[ key ] ); + delete gl._renderbuffers_pool[ key ]; + } + else + setTimeout( inner_check_destroy.bind(this), 1000*60 ); + } + + + //create to store depth + /* + if (this.width != renderbuffer.width || this.height != renderbuffer.height ) { + renderbuffer.width = this.width; + renderbuffer.height = this.height; + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, this.width, this.height); + } + */ + + gl.viewport(0, 0, this.width, this.height); + + //if(gl._current_texture_drawto) + // throw("Texture.drawTo: Cannot use drawTo from inside another drawTo"); + + gl._current_texture_drawto = this; + gl._current_fbo_color = framebuffer; + gl._current_fbo_depth = renderbuffer; + + if(this.texture_type == gl.TEXTURE_2D) + { + if( this.format !== gl.DEPTH_COMPONENT ) + { + gl.framebufferTexture2D( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.handler, 0 ); + gl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, renderbuffer ); + } + else + { + gl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, renderbuffer ); + gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, this.handler, 0); + } + callback(this, params); + } + else if(this.texture_type == gl.TEXTURE_CUBE_MAP) + { + //bind the fixed ones out of the loop to save calls + if( this.format !== gl.DEPTH_COMPONENT ) + gl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, renderbuffer ); + else + gl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, renderbuffer ); + + //for every face of the cubemap + for(var i = 0; i < 6; i++) + { + if( this.format !== gl.DEPTH_COMPONENT ) + gl.framebufferTexture2D( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, this.handler, 0); + else + gl.framebufferTexture2D( gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, this.handler, 0 ); + callback(this,i, params); + } + } + + this.data = null; + + gl._current_texture_drawto = null; + gl._current_fbo_color = null; + gl._current_fbo_depth = null; + + gl.bindFramebuffer( gl.FRAMEBUFFER, old_fbo ); + gl.bindRenderbuffer(gl.RENDERBUFFER, null); + gl.viewport(v[0], v[1], v[2], v[3]); + + return this; +} + +/** +* Static version of drawTo meant to be used with several buffers +* @method drawToColorAndDepth +* @param {Texture} color_texture +* @param {Texture} depth_texture +* @param {Function} callback +*/ +Texture.drawTo = function( color_textures, callback, depth_texture ) +{ + var w = -1, + h = -1, + type = null; + + if(!color_textures && !depth_texture) + throw("Textures missing in drawTo"); + + if(color_textures && color_textures.length) + { + for(var i = 0; i < color_textures.length; i++) + { + var t = color_textures[i]; + if(w == -1) + w = t.width; + else if(w != t.width) + throw("Cannot use Texture.drawTo if textures have different dimensions"); + if(h == -1) + h = t.height; + else if(h != t.height) + throw("Cannot use Texture.drawTo if textures have different dimensions"); + if(type == null) //first one defines the type + type = t.type; + else if (type != t.type) + throw("Cannot use Texture.drawTo if textures have different data type, all must have the same type"); + } + } + else + { + w = depth_texture.width; + h = depth_texture.height; + } + + var ext = gl.extensions["WEBGL_draw_buffers"]; + if(!ext && color_textures && color_textures.length > 1) + throw("Rendering to several textures not supported"); + + var v = gl.getViewport(); + gl._framebuffer = gl._framebuffer || gl.createFramebuffer(); + gl.bindFramebuffer( gl.FRAMEBUFFER, gl._framebuffer ); + + gl.viewport( 0, 0, w, h ); + + var renderbuffer = null; + if( depth_texture && depth_texture.format !== gl.DEPTH_COMPONENT || depth_texture.type != gl.UNSIGNED_INT ) + throw("Depth texture must be of format: gl.DEPTH_COMPONENT and type: gl.UNSIGNED_INT"); + + if( depth_texture ) + { + gl.framebufferTexture2D( gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, depth_texture.handler, 0); + } + else //create a temporary depth renderbuffer + { + //create renderbuffer for depth + renderbuffer = gl._renderbuffer = gl._renderbuffer || gl.createRenderbuffer(); + renderbuffer.width = w; + renderbuffer.height = h; + gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuffer ); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, w, h); + + gl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, renderbuffer ); + } + + if( color_textures ) + { + var order = []; //draw_buffers request the use of an array with the order of the attachments + for(var i = 0; i < color_textures.length; i++) + { + var t = color_textures[i]; + gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, gl.TEXTURE_2D, t.handler, 0); + order.push( gl.COLOR_ATTACHMENT0 + i ); + } + + if(color_textures.length > 1) + ext.drawBuffersWEBGL( order ); + } + else //create temporary color render buffer + { + var color_renderbuffer = this._color_renderbuffer = this._color_renderbuffer || gl.createRenderbuffer(); + color_renderbuffer.width = w; + color_renderbuffer.height = h; + + gl.bindRenderbuffer( gl.RENDERBUFFER, color_renderbuffer ); + gl.renderbufferStorage( gl.RENDERBUFFER, gl.RGBA4, w, h ); + + gl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, color_renderbuffer ); + } + + var complete = gl.checkFramebufferStatus( gl.FRAMEBUFFER ); + if(complete !== gl.FRAMEBUFFER_COMPLETE) + throw("FBO not complete: " + complete); + + callback(); + + //clear data + if(color_textures.length) + for(var i = 0; i < color_textures.length; ++i) + color_textures[i].data = null; + + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + gl.viewport(v[0], v[1], v[2], v[3]); +} + +/** +* Similar to drawTo but it also stores the depth in a depth texture +* @method drawToColorAndDepth +* @param {Texture} color_texture +* @param {Texture} depth_texture +* @param {Function} callback +*/ +Texture.drawToColorAndDepth = function( color_texture, depth_texture, callback ) { + var gl = color_texture.gl; //static function + + if(depth_texture.width != color_texture.width || depth_texture.height != color_texture.height) + throw("Different size between color texture and depth texture"); + + var v = gl.getViewport(); + + gl._framebuffer = gl._framebuffer || gl.createFramebuffer(); + + gl.bindFramebuffer( gl.FRAMEBUFFER, gl._framebuffer); + + gl.viewport(0, 0, color_texture.width, color_texture.height); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, color_texture.handler, 0); + gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, depth_texture.handler, 0); + + callback(); + + color_texture.data = null; + depth_texture.data = null; + + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + gl.viewport(v[0], v[1], v[2], v[3]); +} + + + +/** +* Copy content of one texture into another +* TODO: check using copyTexImage2D +* @method copyTo +* @param {GL.Texture} target_texture +* @param {GL.Shader} [shader=null] optional shader to apply while copying +* @param {Object} [uniforms=null] optional uniforms for the shader +*/ +Texture.prototype.copyTo = function( target_texture, shader, uniforms ) { + var that = this; + var gl = this.gl; + if(!target_texture) + throw("target_texture required"); + + //save state + var previous_fbo = gl.getParameter( gl.FRAMEBUFFER_BINDING ); + var viewport = gl.getViewport(); + + if(!shader) + shader = this.texture_type == gl.TEXTURE_2D ? GL.Shader.getScreenShader() : GL.Shader.getCubemapCopyShader(); + + //render + gl.disable( gl.BLEND ); + gl.disable( gl.DEPTH_TEST ); + if(shader && uniforms) + shader.uniforms( uniforms ); + + //reuse fbo + var fbo = gl.__copy_fbo; + if(!fbo) + fbo = gl.__copy_fbo = gl.createFramebuffer(); + gl.bindFramebuffer( gl.FRAMEBUFFER, fbo ); + + gl.viewport(0,0,target_texture.width, target_texture.height); + if(this.texture_type == gl.TEXTURE_2D) + { + //regular color texture + if(this.format !== gl.DEPTH_COMPONENT && this.format !== gl.DEPTH_STENCIL ) + { + /* doesnt work + if( this.width == target_texture.width && this.height == target_texture.height && this.format == target_texture.format) + { + gl.framebufferTexture2D( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.handler, 0); + gl.bindTexture( target_texture.texture_type, target_texture.handler ); + gl.copyTexImage2D( target_texture.texture_type, 0, this.format, 0, 0, target_texture.width, target_texture.height, 0); + } + else + */ + { + gl.framebufferTexture2D( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, target_texture.handler, 0); + this.toViewport( shader ); + } + } + else //copying a depth texture is harder + { + var color_renderbuffer = gl._color_renderbuffer = gl._color_renderbuffer || gl.createRenderbuffer(); + var w = color_renderbuffer.width = target_texture.width; + var h = color_renderbuffer.height = target_texture.height; + + //attach color render buffer + gl.bindRenderbuffer( gl.RENDERBUFFER, color_renderbuffer ); + gl.renderbufferStorage( gl.RENDERBUFFER, gl.RGBA4, w, h ); + gl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, color_renderbuffer ); + + //attach depth texture + var attachment_point = target_texture.format == gl.DEPTH_STENCIL ? gl.DEPTH_STENCIL_ATTACHMENT : gl.DEPTH_ATTACHMENT; + gl.framebufferTexture2D( gl.FRAMEBUFFER, attachment_point, gl.TEXTURE_2D, target_texture.handler, 0); + + var complete = gl.checkFramebufferStatus( gl.FRAMEBUFFER ); + if(complete !== gl.FRAMEBUFFER_COMPLETE) + throw("FBO not complete: " + complete); + + //enable depth test? + gl.enable( gl.DEPTH_TEST ); + gl.depthFunc( gl.ALWAYS ); + gl.colorMask( false,false,false,false ); + //call shader that overwrites depth values + shader = GL.Shader.getCopyDepthShader(); + this.toViewport( shader ); + gl.colorMask( true,true,true,true ); + gl.disable( gl.DEPTH_TEST ); + gl.depthFunc( gl.LEQUAL ); + gl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, null ); + gl.framebufferTexture2D( gl.FRAMEBUFFER, attachment_point, gl.TEXTURE_2D, null, 0); + } + } + else if(this.texture_type == gl.TEXTURE_CUBE_MAP) + { + shader.uniforms({u_texture: 0}); + var rot_matrix = GL.temp_mat3; + for(var i = 0; i < 6; i++) + { + gl.framebufferTexture2D( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, target_texture.handler, 0); + var face_info = GL.Texture.cubemap_camera_parameters[ i ]; + mat3.identity( rot_matrix ); + rot_matrix.set( face_info.right, 0 ); + rot_matrix.set( face_info.up, 3 ); + rot_matrix.set( face_info.dir, 6 ); + //mat3.invert(rot_matrix,rot_matrix); + this.toViewport( shader,{ u_rotation: rot_matrix }); + } + } + + //restore previous state + gl.setViewport(viewport); //restore viewport + gl.bindFramebuffer( gl.FRAMEBUFFER, previous_fbo ); //restore fbo + + //generate mipmaps when needed + if (target_texture.minFilter && target_texture.minFilter != gl.NEAREST && target_texture.minFilter != gl.LINEAR) { + target_texture.bind(); + gl.generateMipmap(target_texture.texture_type); + target_texture.has_mipmaps = true; + } + + target_texture.data = null; + gl.bindTexture( target_texture.texture_type, null ); //disable + return this; +} + + +/** +* Similar to CopyTo, but more specific, only for color texture_2D. It doesnt change the blend flag +* @method blit +* @param {GL.Texture} target_texture +* @param {GL.Shader} [shader=null] optional shader to apply while copying +* @param {Object} [uniforms=null] optional uniforms for the shader +*/ +Texture.prototype.blit = (function(){ + var viewport = new Float32Array(4); + + return function( target_texture, shader, uniforms ) { + var that = this; + var gl = this.gl; + + if ( this.texture_type != gl.TEXTURE_2D || this.format === gl.DEPTH_COMPONENT || this.format === gl.DEPTH_STENCIL ) + throw("blit only support TEXTURE_2D of RGB or RGBA. use copyTo instead"); + + //save state + var previous_fbo = gl.getParameter( gl.FRAMEBUFFER_BINDING ); + viewport.set( gl.viewport_data ); + + shader = shader || GL.Shader.getScreenShader(); + if(shader && uniforms) + shader.uniforms( uniforms ); + + //reuse fbo + var fbo = gl.__copy_fbo; + if(!fbo) + fbo = gl.__copy_fbo = gl.createFramebuffer(); + gl.bindFramebuffer( gl.FRAMEBUFFER, fbo ); + + gl.viewport(0,0,target_texture.width, target_texture.height); + gl.framebufferTexture2D( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, target_texture.handler, 0); + + this.bind(0); + shader.draw( GL.Mesh.getScreenQuad(), gl.TRIANGLES ); + + //restore previous state + gl.setViewport(viewport); //restore viewport + gl.bindFramebuffer( gl.FRAMEBUFFER, previous_fbo ); //restore fbo + + target_texture.data = null; + gl.bindTexture( target_texture.texture_type, null ); //disable + return this; + } +})(); + +/** +* Render texture in a quad to full viewport size +* @method toViewport +* @param {Shader} shader to apply, otherwise a default textured shader is applied [optional] +* @param {Object} uniforms for the shader if needed [optional] +*/ +Texture.prototype.toViewport = function(shader, uniforms) +{ + shader = shader || Shader.getScreenShader(); + var mesh = Mesh.getScreenQuad(); + this.bind(0); + //shader.uniforms({u_texture: 0}); //never changes + if(uniforms) + shader.uniforms(uniforms); + shader.draw( mesh, gl.TRIANGLES ); +} + +/** +* Fills the texture with a constant color (uses gl.clear) +* @method fill +* @param {vec4} color rgba +* @param {boolean} skip_mipmaps if true the mipmaps wont be updated +*/ +Texture.prototype.fill = function(color, skip_mipmaps ) +{ + var old_color = gl.getParameter( gl.COLOR_CLEAR_VALUE ); + gl.clearColor( color[0], color[1], color[2], color[3] ); + this.drawTo( function() { + gl.clear( gl.COLOR_BUFFER_BIT ); + }); + gl.clearColor( old_color[0], old_color[1], old_color[2], old_color[3] ); + + if (!skip_mipmaps && this.minFilter && this.minFilter != gl.NEAREST && this.minFilter != gl.LINEAR ) { + this.bind(); + gl.generateMipmap( this.texture_type ); + this.has_mipmaps = true; + } +} + +/** +* Render texture in a quad of specified area +* @method renderQuad +* @param {number} x +* @param {number} y +* @param {number} width +* @param {number} height +*/ +Texture.prototype.renderQuad = (function() { + //static variables: less garbage + var identity = mat3.create(); + var pos = vec2.create(); + var size = vec2.create(); + var white = vec4.fromValues(1,1,1,1); + + return (function(x,y,w,h, shader, uniforms) + { + pos[0] = x; pos[1] = y; + size[0] = w; size[1] = h; + + shader = shader || Shader.getQuadShader(this.gl); + var mesh = Mesh.getScreenQuad(this.gl); + this.bind(0); + shader.uniforms({u_texture: 0, u_position: pos, u_color: white, u_size: size, u_viewport: gl.viewport_data.subarray(2,4), u_transform: identity }); + if(uniforms) + shader.uniforms(uniforms); + shader.draw( mesh, gl.TRIANGLES ); + }); +})(); + + +/** +* Applies a blur filter of 5x5 pixels to the texture (be careful using it, it is slow) +* @method applyBlur +* @param {Number} offsetx scalar that multiplies the offset when fetching pixels horizontally (default 1) +* @param {Number} offsety scalar that multiplies the offset when fetching pixels vertically (default 1) +* @param {Number} intensity scalar that multiplies the result (default 1) +* @param {Texture} output_texture [optional] if not passed the output is the own texture +* @param {Texture} temp_texture blur needs a temp texture, if not supplied it will use the temporary textures pool +* @return {Texture} returns the temp_texture in case you want to reuse it +*/ +Texture.prototype.applyBlur = function( offsetx, offsety, intensity, output_texture, temp_texture ) +{ + var that = this; + var gl = this.gl; + if(offsetx === undefined) + offsetx = 1; + if(offsety === undefined) + offsety = 1; + gl.disable( gl.DEPTH_TEST ); + gl.disable( gl.BLEND ); + output_texture = output_texture || this; + var is_temp = !temp_texture; + + //if(this === output_texture && this.texture_type === gl.TEXTURE_CUBE_MAP ) + // throw("cannot use applyBlur in a texture with itself when blurring a CUBE_MAP"); + if(temp_texture === output_texture) + throw("cannot use applyBlur in a texture using as temporary itself"); + + if(output_texture && this.texture_type !== output_texture.texture_type ) + throw("cannot use applyBlur with textures of different texture_type"); + + //if(this.width != output_texture.width || this.height != output_texture.height) + // throw("cannot use applyBlur with an output texture of different size, it doesnt work"); + + //save state + var current_fbo = gl.getParameter( gl.FRAMEBUFFER_BINDING ); + var viewport = gl.getViewport(); + + //reuse fbo + var fbo = gl.__copy_fbo; + if(!fbo) + fbo = gl.__copy_fbo = gl.createFramebuffer(); + gl.bindFramebuffer( gl.FRAMEBUFFER, fbo ); + gl.viewport(0,0, this.width, this.height); + + if( this.texture_type === gl.TEXTURE_2D ) + { + var shader = GL.Shader.getBlurShader(); + + if(!temp_texture) + temp_texture = GL.Texture.getTemporary( this.width, this.height, this ); + + //horizontal blur + gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, temp_texture.handler, 0); + this.toViewport( shader, {u_texture: 0, u_intensity: intensity, u_offset: [0, offsety / this.height ] }); + + //vertical blur + gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, output_texture.handler, 0); + gl.viewport(0,0,output_texture.width, output_texture.height); + temp_texture.toViewport( shader, {u_intensity: intensity, u_offset: [offsetx / temp_texture.width, 0] }); + + if(is_temp) + GL.Texture.releaseTemporary( temp_texture ); + } + else if( this.texture_type === gl.TEXTURE_CUBE_MAP ) + { + //var weights = new Float32Array([ 0.16/0.98, 0.15/0.98, 0.12/0.98, 0.09/0.98, 0.05/0.98 ]); + //var weights = new Float32Array([ 0.05/0.98, 0.09/0.98, 0.12/0.98, 0.15/0.98, 0.16/0.98, 0.15/0.98, 0.12/0.98, 0.09/0.98, 0.05/0.98, 0.0 ]); //extra 0 to avoid mat3 + var shader = GL.Shader.getCubemapBlurShader(); + shader.uniforms({u_texture: 0, u_intensity: intensity, u_offset: [ offsetx / this.width, offsety / this.height ] }); + this.bind(0); + var mesh = Mesh.getScreenQuad(); + mesh.bindBuffers( shader ); + shader.bind(); + + var destination = null; + + if(!temp_texture && output_texture == this) //we need a temporary texture + destination = temp_texture = GL.Texture.getTemporary( output_texture.width, output_texture.height, output_texture ); + else + destination = output_texture; //blur directly to output texture + + var rot_matrix = GL.temp_mat3; + for(var i = 0; i < 6; ++i) + { + gl.framebufferTexture2D( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, destination.handler, 0); + var face_info = GL.Texture.cubemap_camera_parameters[ i ]; + mat3.identity(rot_matrix); + rot_matrix.set( face_info.right, 0 ); + rot_matrix.set( face_info.up, 3 ); + rot_matrix.set( face_info.dir, 6 ); + //mat3.invert(rot_matrix,rot_matrix); + shader._setUniform( "u_rotation", rot_matrix ); + gl.drawArrays( gl.TRIANGLES, 0, 6 ); + } + + mesh.unbindBuffers( shader ); + + if(temp_texture) //copy back + temp_texture.copyTo( output_texture ); + + if(temp_texture && is_temp) //release temp + GL.Texture.releaseTemporary( temp_texture ); + } + + //restore previous state + gl.setViewport(viewport); //restore viewport + gl.bindFramebuffer( gl.FRAMEBUFFER, current_fbo ); //restore fbo + + output_texture.data = null; + + //generate mipmaps when needed + if (output_texture.minFilter && output_texture.minFilter != gl.NEAREST && output_texture.minFilter != gl.LINEAR) { + output_texture.bind(); + gl.generateMipmap(output_texture.texture_type); + output_texture.has_mipmaps = true; + } + + gl.bindTexture( output_texture.texture_type, null ); //disable +} + + +/** +* Loads and uploads a texture from a url +* @method Texture.fromURL +* @param {String} url +* @param {Object} options +* @param {Function} on_complete +* @return {Texture} the texture +*/ +Texture.fromURL = function( url, options, on_complete, gl ) { + gl = gl || global.gl; + + options = options || {}; + options = Object.create(options); //creates a new options using the old one as prototype + + var texture = options.texture || new GL.Texture(1, 1, options, gl); + + if(url.length < 64) + texture.url = url; + texture.bind(); + var default_color = options.temp_color || Texture.loading_color; + //Texture.setUploadOptions(options); + gl.pixelStorei(gl.UNPACK_ALIGNMENT, 4); + var temp_color = options.type == gl.FLOAT ? new Float32Array(default_color) : new Uint8Array(default_color); + gl.texImage2D( gl.TEXTURE_2D, 0, texture.format, texture.width, texture.height, 0, texture.format, texture.type, temp_color ); + gl.bindTexture( texture.texture_type, null ); //disable + texture.ready = false; + + var ext = null; + if( options.extension ) //to force format + ext = options.extension; + + if(!ext && url.length < 512) //avoid base64 urls + { + var base = url; + var pos = url.indexOf("?"); + if(pos != -1) + base = url.substr(0,pos); + pos = base.lastIndexOf("."); + if(pos != -1) + ext = base.substr(pos+1).toLowerCase(); + } + + if( ext == "dds") + { + var ext = gl.getExtension("WEBKIT_WEBGL_compressed_texture_s3tc") || gl.getExtension("WEBGL_compressed_texture_s3tc"); + var new_texture = new GL.Texture(0,0, options, gl); + DDS.loadDDSTextureEx(gl, ext, url, new_texture.handler, true, function(t) { + texture.texture_type = t.texture_type; + texture.handler = t; + delete texture["ready"]; //texture.ready = true; + if(on_complete) + on_complete(texture, url); + }); + } + else if( ext == "tga" ) + { + HttpRequest( url, null, function(data) { + var img_data = GL.Texture.parseTGA(data); + if(!img_data) + return; + options.texture = texture; + if(img_data.format == "RGB") + texture.format = gl.RGB; + texture = GL.Texture.fromMemory( img_data.width, img_data.height, img_data.pixels, options ); + delete texture["ready"]; //texture.ready = true; + if(on_complete) + on_complete( texture, url ); + },null,{ binary: true }); + } + else //png,jpg,webp,... + { + var image = new Image(); + image.src = url; + var that = this; + image.onload = function() + { + options.texture = texture; + GL.Texture.fromImage(this, options); + delete texture["ready"]; //texture.ready = true; + if(on_complete) + on_complete(texture, url); + } + image.onerror = function() + { + if(on_complete) + on_complete(null); + } + } + + return texture; +}; + +Texture.parseTGA = function(data) +{ + if(!data || data.constructor !== ArrayBuffer) + throw( "TGA: data must be ArrayBuffer"); + data = new Uint8Array(data); + var TGAheader = new Uint8Array( [0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0] ); + var TGAcompare = data.subarray(0,12); + for(var i = 0; i < TGAcompare.length; i++) + if(TGAheader[i] != TGAcompare[i]) + { + console.error("TGA header is not valid"); + return null; //not a TGA + } + + var header = data.subarray(12,18); + var img = {}; + img.width = header[1] * 256 + header[0]; + img.height = header[3] * 256 + header[2]; + img.bpp = header[4]; + img.bytesPerPixel = img.bpp / 8; + img.imageSize = img.width * img.height * img.bytesPerPixel; + img.pixels = data.subarray(18,18+img.imageSize); + img.pixels = new Uint8Array( img.pixels ); //clone + if( (header[5] & (1<<4)) == 0) //hack, needs swap + { + //TGA comes in BGR format so we swap it, this is slooooow + for(var i = 0; i < img.imageSize; i+= img.bytesPerPixel) + { + var temp = img.pixels[i]; + img.pixels[i] = img.pixels[i+2]; + img.pixels[i+2] = temp; + } + header[5] |= 1<<4; //mark as swaped + img.format = img.bpp == 32 ? "RGBA" : "RGB"; + } + else + img.format = img.bpp == 32 ? "RGBA" : "RGB"; + //some extra bytes to avoid alignment problems + //img.pixels = new Uint8Array( img.imageSize + 14); + //img.pixels.set( data.subarray(18,18+img.imageSize), 0); + img.flipY = true; + //img.format = img.bpp == 32 ? "BGRA" : "BGR"; + //trace("TGA info: " + img.width + "x" + img.height ); + return img; +} + +/** +* Create a texture from an Image +* @method Texture.fromImage +* @param {Image} image +* @param {Object} options +* @return {Texture} the texture +*/ +Texture.fromImage = function( image, options ) { + options = options || {}; + + var texture = options.texture || new GL.Texture( image.width, image.height, options); + texture.uploadImage( image, options ); + + texture.bind(); + gl.texParameteri(texture.texture_type, gl.TEXTURE_MAG_FILTER, texture.magFilter ); + gl.texParameteri(texture.texture_type, gl.TEXTURE_MIN_FILTER, texture.minFilter ); + gl.texParameteri(texture.texture_type, gl.TEXTURE_WRAP_S, texture.wrapS ); + gl.texParameteri(texture.texture_type, gl.TEXTURE_WRAP_T, texture.wrapT ); + + if (GL.isPowerOfTwo(texture.width) && GL.isPowerOfTwo(texture.height) ) + { + if( options.minFilter && options.minFilter != gl.NEAREST && options.minFilter != gl.LINEAR) + { + texture.bind(); + gl.generateMipmap(texture.texture_type); + texture.has_mipmaps = true; + } + } + else + { + //no mipmaps supported + gl.texParameteri(texture.texture_type, gl.TEXTURE_MIN_FILTER, GL.LINEAR ); + gl.texParameteri(texture.texture_type, gl.TEXTURE_WRAP_S, GL.CLAMP_TO_EDGE ); + gl.texParameteri(texture.texture_type, gl.TEXTURE_WRAP_T, GL.CLAMP_TO_EDGE ); + texture.has_mipmaps = false; + } + gl.bindTexture(texture.texture_type, null); //disable + texture.data = image; + if(options.keep_image) + texture.img = image; + return texture; +}; + +/** +* Create a texture from a Video +* @method Texture.fromVideo +* @param {Video} video +* @param {Object} options +* @return {Texture} the texture +*/ +Texture.fromVideo = function(video, options) { + options = options || {}; + + var texture = options.texture || new GL.Texture(video.videoWidth, video.videoHeight, options); + texture.bind(); + texture.uploadImage( video, options ); + if (options.minFilter && options.minFilter != gl.NEAREST && options.minFilter != gl.LINEAR) { + texture.bind(); + gl.generateMipmap(texture.texture_type); + texture.has_mipmaps = true; + texture.data = video; + } + gl.bindTexture(texture.texture_type, null); //disable + return texture; +}; + +/** +* Create a clone of a texture +* @method Texture.fromTexture +* @param {Texture} old_texture +* @param {Object} options +* @return {Texture} the texture +*/ +Texture.fromTexture = function( old_texture, options) { + options = options || {}; + var texture = new GL.Texture( old_texture.width, old_texture.height, options ); + old_texture.copyTo( texture ); + return texture; +}; + +Texture.prototype.clone = function( options ) +{ + var old_options = this.getProperties(); + if(options) + for(var i in options) + old_options[i] = options[i]; + return Texture.fromTexture( this, old_options); +} + +/** +* Create a texture from an ArrayBuffer containing the pixels +* @method Texture.fromTexture +* @param {number} width +* @param {number} height +* @param {ArrayBuffer} pixels +* @param {Object} options +* @return {Texture} the texture +*/ +Texture.fromMemory = function( width, height, pixels, options) //format in options as format +{ + options = options || {}; + + var texture = options.texture || new GL.Texture(width, height, options); + Texture.setUploadOptions(options); + texture.bind(); + + if(pixels.constructor === Array) + { + if(options.type == gl.FLOAT) + pixels = new Float32Array( pixels ); + else if(options.type == GL.HALF_FLOAT || options.type == GL.HALF_FLOAT_OES) + pixels = new Uint16Array( pixels ); //gl.UNSIGNED_SHORT_4_4_4_4 is only for texture that are SHORT per pixel, not per channel! + else + pixels = new Uint8Array( pixels ); + } + + gl.texImage2D( gl.TEXTURE_2D, 0, texture.format, width, height, 0, texture.format, texture.type, pixels ); + texture.width = width; + texture.height = height; + texture.data = pixels; + if (options.minFilter && options.minFilter != gl.NEAREST && options.minFilter != gl.LINEAR) { + gl.generateMipmap(gl.TEXTURE_2D); + texture.has_mipmaps = true; + } + gl.bindTexture(texture.texture_type, null); //disable + return texture; +}; + +/** +* Create a texture from an ArrayBuffer containing the pixels +* @method Texture.fromDDSInMemory +* @param {ArrayBuffer} DDS data +* @param {Object} options +* @return {Texture} the texture +*/ +Texture.fromDDSInMemory = function(data, options) //format in options as format +{ + options = options || {}; + + var texture = options.texture || new GL.Texture(0, 0, options); + GL.Texture.setUploadOptions(options); + texture.bind(); + + var ext = gl.getExtension("WEBKIT_WEBGL_compressed_texture_s3tc") || gl.getExtension("WEBGL_compressed_texture_s3tc"); + DDS.loadDDSTextureFromMemoryEx(gl, ext, data, texture, true ); + + gl.bindTexture(texture.texture_type, null); //disable + return texture; +}; + +/** +* Create a generative texture from a shader ( must GL.Shader.getScreenShader as reference for the shader ) +* @method Texture.fromShader +* @param {number} width +* @param {number} height +* @param {Shader} shader +* @param {Object} options +* @return {Texture} the texture +*/ +Texture.fromShader = function(width, height, shader, options) { + options = options || {}; + + var texture = new GL.Texture( width, height, options ); + //copy content + texture.drawTo(function() { + gl.disable( gl.BLEND ); + gl.disable( gl.DEPTH_TEST ); + gl.disable( gl.CULL_FACE ); + var mesh = Mesh.getScreenQuad(); + shader.draw( mesh ); + }); + + return texture; +}; + +/** +* Create a cubemap texture from a set of 6 images +* @method Texture.cubemapFromImages +* @param {Array} images +* @param {Object} options +* @return {Texture} the texture +*/ +Texture.cubemapFromImages = function(images, options) { + options = options || {}; + if(images.length != 6) + throw "missing images to create cubemap"; + + var width = images[0].width; + var height = images[0].height; + options.texture_type = gl.TEXTURE_CUBE_MAP; + + var texture = null; + + if(options.texture) + { + texture = options.texture; + texture.width = width; + texture.height = height; + } + else + texture = new GL.Texture( width, height, options ); + + Texture.setUploadOptions(options); + texture.bind(); + + try { + + for(var i = 0; i < 6; i++) + gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X+i, 0, texture.format, texture.format, texture.type, images[i]); + texture.data = images; + } catch (e) { + if (location.protocol == 'file:') { + throw 'image not loaded for security reasons (serve this page over "http://" instead)'; + } else { + throw 'image not loaded for security reasons (image must originate from the same ' + + 'domain as this page or use Cross-Origin Resource Sharing)'; + } + } + if (options.minFilter && options.minFilter != gl.NEAREST && options.minFilter != gl.LINEAR) { + gl.generateMipmap(gl.TEXTURE_CUBE_MAP); + texture.has_mipmaps = true; + } + + texture.unbind(); + return texture; +}; + +/** +* Create a cubemap texture from a single image that contains all six images +* If it is a cross, it must be horizontally aligned, and options.is_cross must be equal to the column where the top and bottom are located (usually 1 or 2) +* otherwise it assumes the 6 images are arranged vertically, in the order of OpenGL: +X, -X, +Y, -Y, +Z, -Z +* @method Texture.cubemapFromImage +* @param {Image} image +* @param {Object} options +* @return {Texture} the texture +*/ +Texture.cubemapFromImage = function( image, options ) { + options = options || {}; + + if(image.width != (image.height / 6) && image.height % 6 != 0 && !options.faces && !options.is_polar ) + { + console.error( "Cubemap image not valid, only 1x6 (vertical) or 6x3 (cross) formats. Check size:", image.width, image.height ); + return null; + } + + var width = image.width; + var height = image.height; + + if(options.is_polar) + { + var size = options.size || GL.nearestPowerOfTwo( image.height ); + var temp_tex = GL.Texture.fromImage( image, { ignore_pot:true, wrap: gl.REPEAT, filter: gl.LINEAR } ); + var cubemap = new GL.Texture( size, size, { texture_type: gl.TEXTURE_CUBE_MAP, format: gl.RGBA }); + if(options.texture) + { + var old_tex = options.texture; + for(var i in cubemap) + old_tex[i] = cubemap[i]; + cubemap = old_tex; + } + var rot_matrix = mat3.create(); + var uniforms = { u_texture:0, u_rotation: rot_matrix }; + gl.disable( gl.DEPTH_TEST ); + gl.disable( gl.BLEND ); + var shader = GL.Shader.getPolarToCubemapShader(); + cubemap.drawTo(function(t,i){ + var face_info = GL.Texture.cubemap_camera_parameters[ i ]; + mat3.identity( rot_matrix ); + rot_matrix.set( face_info.right, 0 ); + rot_matrix.set( face_info.up, 3 ); + rot_matrix.set( face_info.dir, 6 ); + temp_tex.toViewport( shader, uniforms ); + }); + if(options.keep_image) + cubemap.img = image; + return cubemap; + } + else if(options.is_cross !== undefined) + { + options.faces = Texture.generateCubemapCrossFacesInfo(image.width, options.is_cross); + width = height = image.width / 4; + } + else if(options.faces) + { + width = options.width || options.faces[0].width; + height = options.height || options.faces[0].height; + } + else + height /= 6; + + if(width != height) + { + console.log("Texture not valid, width and height for every face must be square"); + return null; + } + + var size = width; + options.no_flip = true; + + var images = []; + for(var i = 0; i < 6; i++) + { + var canvas = createCanvas( size, size ); + var ctx = canvas.getContext("2d"); + if(options.faces) + ctx.drawImage(image, options.faces[i].x, options.faces[i].y, options.faces[i].width || size, options.faces[i].height || size, 0,0, size, size ); + else + ctx.drawImage(image, 0, height*i, width, height, 0,0, size, size ); + images.push(canvas); + //document.body.appendChild(canvas); //debug + } + + var texture = Texture.cubemapFromImages(images, options); + if(options.keep_image) + texture.img = image; + return texture; +}; + +/** +* Given the width and the height of an image, and in which column is the top and bottom sides of the cubemap, it gets the info to pass to Texture.cubemapFromImage in options.faces +* @method Texture.generateCubemapCrossFaces +* @param {number} width of the CROSS image (not the side image) +* @param {number} column the column where the top and the bottom is located +* @return {Object} object to pass to Texture.cubemapFromImage in options.faces +*/ +Texture.generateCubemapCrossFacesInfo = function(width, column) +{ + if(column === undefined) + column = 1; + var s = width / 4; + + return [ + { x: 2*s, y: s, width: s, height: s }, //+x + { x: 0, y: s, width: s, height: s }, //-x + { x: column*s, y: 0, width: s, height: s }, //+y + { x: column*s, y: 2*s, width: s, height: s }, //-y + { x: s, y: s, width: s, height: s }, //+z + { x: 3*s, y: s, width: s, height: s } //-z + ]; +} + +/** +* Create a cubemap texture from a single image url that contains the six images +* if it is a cross, it must be horizontally aligned, and options.is_cross must be equal to the column where the top and bottom are located (usually 1 or 2) +* otherwise it assumes the 6 images are arranged vertically. +* @method Texture.cubemapFromURL +* @param {Image} image +* @param {Object} options +* @param {Function} on_complete callback +* @return {Texture} the texture +*/ +Texture.cubemapFromURL = function( url, options, on_complete ) { + options = options || {}; + options = Object.create(options); //creates a new options using the old one as prototype + options.texture_type = gl.TEXTURE_CUBE_MAP; + var texture = options.texture || new GL.Texture(1, 1, options); + + texture.bind(); + Texture.setUploadOptions(options); + var default_color = options.temp_color || [0,0,0,255]; + var temp_color = options.type == gl.FLOAT ? new Float32Array(default_color) : new Uint8Array(default_color); + + for(var i = 0; i < 6; i++) + gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X+i, 0, texture.format, 1, 1, 0, texture.format, texture.type, temp_color); + gl.bindTexture(texture.texture_type, null); //disable + texture.ready = false; + + var image = new Image(); + image.src = url; + var that = this; + image.onload = function() + { + options.texture = texture; + texture = GL.Texture.cubemapFromImage(this, options); + if(texture) + delete texture["ready"]; //texture.ready = true; + if(on_complete) + on_complete(texture); + } + + return texture; +}; + +/** +* returns an ArrayBuffer with the pixels in the texture, they are fliped in Y +* Warn: If cubemap it only returns the pixels of the first face! use getCubemapPixels instead +* @method getPixels +* @param {number} cubemap_face [optional] the index of the cubemap face to read (ignore if texture_2D) +* @param {number} mipmap level [optional, default is 0] +* @return {ArrayBuffer} the data ( Uint8Array, Uint16Array or Float32Array ) +*/ +Texture.prototype.getPixels = function( cubemap_face, mipmap_level ) +{ + mipmap_level = mipmap_level || 0; + var gl = this.gl; + var v = gl.getViewport(); + var old_fbo = gl.getParameter( gl.FRAMEBUFFER_BINDING ); + + if(this.format == gl.DEPTH_COMPONENT) + throw("cannot use getPixels in depth textures"); + + gl.disable( gl.DEPTH_TEST ); + + //reuse fbo + var fbo = gl.__copy_fbo; + if(!fbo) + fbo = gl.__copy_fbo = gl.createFramebuffer(); + gl.bindFramebuffer( gl.FRAMEBUFFER, fbo ); + + var buffer = null; + + var width = this.width >> mipmap_level; + var height = this.height >> mipmap_level; + gl.viewport(0, 0, width, height); + + if(this.texture_type == gl.TEXTURE_2D) + gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.handler, mipmap_level); + else if(this.texture_type == gl.TEXTURE_CUBE_MAP) + gl.framebufferTexture2D( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_CUBE_MAP_POSITIVE_X + (cubemap_face || 0), this.handler, mipmap_level); + + var channels = this.format == gl.RGB ? 3 : 4; + channels = 4; //WEBGL DOES NOT SUPPORT READING 3 CHANNELS ONLY, YET... + var type = this.type; + //type = gl.UNSIGNED_BYTE; //WEBGL DOES NOT SUPPORT READING FLOAT seems, YET... 23/5/18 now it seems it does now + + if(type == gl.UNSIGNED_BYTE) + buffer = new Uint8Array( width * height * channels ); + else if(type == GL.HALF_FLOAT || type == GL.HALF_FLOAT_OES) //previously half float couldnot be read + buffer = new Uint16Array( width * height * channels ); //gl.UNSIGNED_SHORT_4_4_4_4 is only for texture that are SHORT per pixel, not per channel! + else + buffer = new Float32Array( width * height * channels ); + + gl.readPixels( 0,0, width, height, channels == 3 ? gl.RGB : gl.RGBA, type, buffer ); //NOT SUPPORTED FLOAT or RGB BY WEBGL YET + + //restore + gl.bindFramebuffer(gl.FRAMEBUFFER, old_fbo ); + gl.viewport(v[0], v[1], v[2], v[3]); + return buffer; +} + +/** +* uploads some pixels to the texture (see uploadData method for more options) +* @method setPixels +* @param {ArrayBuffer} data gl.UNSIGNED_BYTE or gl.FLOAT data +* @param {Boolean} no_flip do not flip in Y +* @param {Boolean} skip_mipmaps do not update mipmaps when possible +* @param {Number} cubemap_face if the texture is a cubemap, which face +*/ +Texture.prototype.setPixels = function( data, no_flip, skip_mipmaps, cubemap_face ) +{ + var options = { no_flip: no_flip }; + if(cubemap_face) + options.cubemap_face = cubemap_face; + this.uploadData( data, options, skip_mipmaps ); +} + +/** +* returns an array with six arrays containing the pixels of every cubemap face +* @method getCubemapPixels +* @return {Array} the array that has 6 typed arrays containing the pixels +*/ +Texture.prototype.getCubemapPixels = function() +{ + if(this.texture_type !== gl.TEXTURE_CUBE_MAP) + throw("this texture is not a cubemap"); + return [ this.getPixels(0), this.getPixels(1), this.getPixels(2), this.getPixels(3), this.getPixels(4), this.getPixels(5) ]; +} + +/** +* fills a cubemap given an array with typed arrays containing the pixels of 6 faces +* @method setCubemapPixels +* @param {Array} data array that has 6 typed arrays containing the pixels +* @param {bool} noflip if pixels should not be flipped according to Y +*/ +Texture.prototype.setCubemapPixels = function( data_array, no_flip ) +{ + if(this.texture_type !== gl.TEXTURE_CUBE_MAP) + throw("this texture is not a cubemap, it should be created with { texture_type: gl.TEXTURE_CUBE_MAP }"); + for(var i = 0; i < 6; ++i) + this.setPixels( data_array[i], no_flip, i != 5, i ); +} + +/** +* Copy texture content to a canvas +* @method toCanvas +* @param {Canvas} canvas must have the same size, if different the canvas will be resized +* @param {boolean} flip_y optional, flip vertically +* @param {Number} max_size optional, if it is supplied the canvas wont be bigger of max_size (the image will be scaled down) +*/ +Texture.prototype.toCanvas = function( canvas, flip_y, max_size ) +{ + max_size = max_size || 8192; + var gl = this.gl; + + var w = Math.min( this.width, max_size ); + var h = Math.min( this.height, max_size ); + + //cross + if(this.texture_type == gl.TEXTURE_CUBE_MAP) + { + w = w * 4; + h = h * 3; + } + + canvas = canvas || createCanvas( w, h ); + if(canvas.width != w) + canvas.width = w; + if(canvas.height != h) + canvas.height = h; + + var buffer = null; + if(this.texture_type == gl.TEXTURE_2D ) + { + if(this.width != w || this.height != h || this.type != gl.UNSIGNED_BYTE) //resize image to fit the canvas + { + //create a temporary texture + var temp = new GL.Texture(w,h,{ format: gl.RGBA, filter: gl.NEAREST }); + this.copyTo( temp ); + buffer = temp.getPixels(); + } + else + buffer = this.getPixels(); + + var ctx = canvas.getContext("2d"); + var pixels = ctx.getImageData(0,0,w,h); + pixels.data.set( buffer ); + ctx.putImageData(pixels,0,0); + + if(flip_y) + { + var temp = createCanvas(w,h); + var temp_ctx = temp.getContext("2d"); + temp_ctx.translate(0,temp.height); + temp_ctx.scale(1,-1); + temp_ctx.drawImage( canvas, 0, 0, temp.width, temp.height ); + ctx.clearRect(0,0,ctx.canvas.width,ctx.canvas.height); + ctx.drawImage( temp, 0, 0 ); + } + } + else if(this.texture_type == gl.TEXTURE_CUBE_MAP ) + { + var temp_canvas = createCanvas( this.width, this.height ); + var temp_ctx = temp_canvas.getContext("2d"); + var info = GL.Texture.generateCubemapCrossFacesInfo( canvas.width, 1 ); + var ctx = canvas.getContext("2d"); + ctx.fillStyle = "black"; + ctx.fillRect(0,0,canvas.width, canvas.height ); + + var cubemap = this; + if(this.type != gl.UNSIGNED_BYTE) //convert pixels to uint8 as it is the only supported format by the canvas + { + //create a temporary texture + cubemap = new GL.Texture( this.width, this.height, { format: gl.RGBA, texture_type: gl.TEXTURE_CUBE_MAP, filter: gl.NEAREST, type: gl.UNSIGNED_BYTE }); + this.copyTo( cubemap ); + } + + for(var i = 0; i < 6; i++) + { + var pixels = temp_ctx.getImageData(0,0, temp_canvas.width, temp_canvas.height ); + buffer = cubemap.getPixels(i); + pixels.data.set( buffer ); + temp_ctx.putImageData(pixels,0,0); + ctx.drawImage( temp_canvas, info[i].x, info[i].y, temp_canvas.width, temp_canvas.height ); + } + } + + return canvas; +} + + +/** +* returns the texture file in binary format +* @method toBinary +* @param {Boolean} flip_y +* @return {ArrayBuffer} the arraybuffer of the file containing the image +*/ +Texture.binary_extension = "png"; +Texture.prototype.toBinary = function(flip_y, type) +{ + //dump to canvas + var canvas = this.toCanvas(null,flip_y); + //use the slow method (because its sync) + var data = canvas.toDataURL( type ); + var index = data.indexOf(","); + var base64_data = data.substr(index+1); + var binStr = atob( base64_data ); + var len = binStr.length, + arr = new Uint8Array(len); + for (var i=0; i 0 ) + console.warn("this texture is already in the textures pool"); + + var pool = gl._texture_pool; + if(!pool) + pool = gl._texture_pool = []; + tex._pool = getTime(); + pool.push( tex ); + + //do not store too much textures in the textures pool + if( pool.length > 20 ) + { + pool.sort( function(a,b) { return b._pool - a._pool } ); //sort by time + //pool.sort( function(a,b) { return a._key - b._key } ); //sort by size + var tex = pool.pop(); //free the last one + tex._pool = 0; + tex.delete(); + } +} + +//returns the next power of two bigger than size +Texture.nextPOT = function( size ) +{ + return Math.pow( 2, Math.ceil( Math.log(size) / Math.log(2) ) ); +} + +/** +* FBO for FrameBufferObjects, FBOs are used to store the render inside one or several textures +* Supports multibuffer and depthbuffer texture, useful for deferred rendering +* @namespace GL +* @class FBO +* @param {Array} color_textures an array containing the color textures, if not supplied a render buffer will be used +* @param {GL.Texture} depth_texture the depth texture, if not supplied a render buffer will be used +* @param {Bool} stencil create a stencil buffer? +* @constructor +*/ +function FBO( textures, depth_texture, stencil, gl ) +{ + gl = gl || global.gl; + this.gl = gl; + this._context_id = gl.context_id; + + if(textures && textures.constructor !== Array) + throw("FBO textures must be an Array"); + + this.handler = null; + this.width = -1; + this.height = -1; + this.color_textures = []; + this.depth_texture = null; + this.stencil = !!stencil; + + this._stencil_enabled = false; + this._num_binded_textures = 0; + + //assign textures + if((textures && textures.length) || depth_texture) + this.setTextures( textures, depth_texture ); + + //save state + this._old_fbo_handler = null; + this._old_viewport = new Float32Array(4); + this.order = null; +} + +GL.FBO = FBO; + +/** +* Changes the textures binded to this FBO +* @method setTextures +* @param {Array} color_textures an array containing the color textures, if not supplied a render buffer will be used +* @param {GL.Texture} depth_texture the depth texture, if not supplied a render buffer will be used +* @param {Boolean} skip_disable it doenst try to go back to the previous FBO enabled in case there was one +*/ +FBO.prototype.setTextures = function( color_textures, depth_texture, skip_disable ) +{ + //test depth + if( depth_texture && depth_texture.constructor === GL.Texture ) + { + if( depth_texture.format !== GL.DEPTH_COMPONENT && + depth_texture.format !== GL.DEPTH_STENCIL && + depth_texture.format !== GL.DEPTH_COMPONENT16 && + depth_texture.format !== GL.DEPTH_COMPONENT24 && + depth_texture.format !== GL.DEPTH_COMPONENT32F ) + throw("FBO Depth texture must be of format: gl.DEPTH_COMPONENT, gl.DEPTH_STENCIL or gl.DEPTH_COMPONENT16/24/32F (only in webgl2)"); + + if( depth_texture.type != GL.UNSIGNED_SHORT && + depth_texture.type != GL.UNSIGNED_INT && + depth_texture.type != GL.UNSIGNED_INT_24_8_WEBGL && + depth_texture.type != GL.FLOAT) + throw("FBO Depth texture must be of type: gl.UNSIGNED_SHORT, gl.UNSIGNED_INT, gl.UNSIGNED_INT_24_8_WEBGL"); + } + + //test if is already binded + var same = this.depth_texture == depth_texture; + if( same && color_textures ) + { + if( color_textures.constructor !== Array ) + throw("FBO: color_textures parameter must be an array containing all the textures to be binded in the color"); + if( color_textures.length == this.color_textures.length ) + { + for(var i = 0; i < color_textures.length; ++i) + if( color_textures[i] != this.color_textures[i] ) + { + same = false; + break; + } + } + else + same = false; + } + + if(this._stencil_enabled !== this.stencil) + same = false; + + if(same) + return; + + //copy textures in place + this.color_textures.length = color_textures ? color_textures.length : 0; + if(color_textures) + for(var i = 0; i < color_textures.length; ++i) + this.color_textures[i] = color_textures[i]; + this.depth_texture = depth_texture; + + //update GPU FBO + this.update( skip_disable ); +} + +/** +* Updates the FBO with the new set of textures and buffers +* @method update +* @param {Boolean} skip_disable it doenst try to go back to the previous FBO enabled in case there was one +*/ +FBO.prototype.update = function( skip_disable ) +{ + //save state to restore afterwards + this._old_fbo_handler = gl.getParameter( gl.FRAMEBUFFER_BINDING ); + + if(!this.handler) + this.handler = gl.createFramebuffer(); + + var w = -1, + h = -1, + type = null; + + var color_textures = this.color_textures; + var depth_texture = this.depth_texture; + + //compute the W and H (and check they have the same size) + if(color_textures && color_textures.length) + for(var i = 0; i < color_textures.length; i++) + { + var t = color_textures[i]; + if(t.constructor !== GL.Texture) + throw("FBO can only bind instances of GL.Texture"); + if(w == -1) + w = t.width; + else if(w != t.width) + throw("Cannot bind textures with different dimensions"); + if(h == -1) + h = t.height; + else if(h != t.height) + throw("Cannot bind textures with different dimensions"); + if(type == null) //first one defines the type + type = t.type; + else if (type != t.type) + throw("Cannot bind textures to a FBO with different pixel formats"); + if (t.texture_type != gl.TEXTURE_2D) + throw("Cannot bind a Cubemap to a FBO"); + } + else + { + w = depth_texture.width; + h = depth_texture.height; + } + + this.width = w; + this.height = h; + + gl.bindFramebuffer( gl.FRAMEBUFFER, this.handler ); + + //draw_buffers allow to have more than one color texture binded in a FBO + var ext = gl.extensions["WEBGL_draw_buffers"]; + if( gl.webgl_version == 1 && !ext && color_textures && color_textures.length > 1) + throw("Rendering to several textures not supported by your browser"); + + var target = gl.webgl_version == 1 ? gl.FRAMEBUFFER : gl.DRAW_FRAMEBUFFER; + + //detach anything bindede + gl.framebufferRenderbuffer( target, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, null ); + gl.framebufferRenderbuffer( target, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, null ); + //detach color too? + + //bind a buffer for the depth + if( depth_texture && depth_texture.constructor === GL.Texture ) + { + if(gl.webgl_version == 1 && !gl.extensions["WEBGL_depth_texture"] ) + throw("Rendering to depth texture not supported by your browser"); + + if(this.stencil && depth_texture.format !== gl.DEPTH_STENCIL ) + console.warn("Stencil cannot be enabled if there is a depth texture with a DEPTH_STENCIL format"); + + if( depth_texture.format == gl.DEPTH_STENCIL ) + gl.framebufferTexture2D( target, gl.DEPTH_STENCIL_ATTACHMENT, gl.TEXTURE_2D, depth_texture.handler, 0); + else + gl.framebufferTexture2D( target, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, depth_texture.handler, 0); + } + else //create a renderbuffer to store depth + { + var depth_renderbuffer = null; + + //allows to reuse a renderbuffer between FBOs + if( depth_texture && depth_texture.constructor === WebGLRenderbuffer && depth_texture.width == w && depth_texture.height == h ) + depth_renderbuffer = this._depth_renderbuffer = depth_texture; + else + { + //create one + depth_renderbuffer = this._depth_renderbuffer = this._depth_renderbuffer || gl.createRenderbuffer(); + depth_renderbuffer.width = w; + depth_renderbuffer.height = h; + } + + gl.bindRenderbuffer( gl.RENDERBUFFER, depth_renderbuffer ); + if(this.stencil) + { + gl.renderbufferStorage( gl.RENDERBUFFER, gl.DEPTH_STENCIL, w, h ); + gl.framebufferRenderbuffer( target, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, depth_renderbuffer ); + } + else + { + gl.renderbufferStorage( gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, w, h ); + gl.framebufferRenderbuffer( target, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depth_renderbuffer ); + } + } + + //bind buffers for the colors + if(color_textures && color_textures.length) + { + this.order = []; //draw_buffers request the use of an array with the order of the attachments + for(var i = 0; i < color_textures.length; i++) + { + var t = color_textures[i]; + + //not a bug, gl.COLOR_ATTACHMENT0 + i because COLOR_ATTACHMENT is sequential numbers + gl.framebufferTexture2D( target, gl.COLOR_ATTACHMENT0 + i, gl.TEXTURE_2D, t.handler, 0 ); + this.order.push( gl.COLOR_ATTACHMENT0 + i ); + } + } + else //create renderbuffer to store color + { + var color_renderbuffer = this._color_renderbuffer = this._color_renderbuffer || gl.createRenderbuffer(); + color_renderbuffer.width = w; + color_renderbuffer.height = h; + gl.bindRenderbuffer( gl.RENDERBUFFER, color_renderbuffer ); + gl.renderbufferStorage( gl.RENDERBUFFER, gl.RGBA4, w, h ); + gl.framebufferRenderbuffer( target, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, color_renderbuffer ); + } + + //detach old ones (only if is reusing a FBO with a different set of textures) + var num = color_textures ? color_textures.length : 0; + for(var i = num; i < this._num_binded_textures; ++i) + gl.framebufferTexture2D( target, gl.COLOR_ATTACHMENT0 + i, gl.TEXTURE_2D, null, 0); + this._num_binded_textures = num; + + this._stencil_enabled = this.stencil; + + /* does not work, must be used with the depth_stencil + if(this.stencil && !depth_texture) + { + var stencil_buffer = this._stencil_buffer = this._stencil_buffer || gl.createRenderbuffer(); + stencil_buffer.width = w; + stencil_buffer.height = h; + gl.bindRenderbuffer( gl.RENDERBUFFER, stencil_buffer ); + gl.renderbufferStorage( gl.RENDERBUFFER, gl.STENCIL_INDEX8, w, h); + gl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.STENCIL_ATTACHMENT, gl.RENDERBUFFER, stencil_buffer ); + this._stencil_enabled = true; + } + else + { + this._stencil_buffer = null; + this._stencil_enabled = false; + } + */ + + //when using more than one texture you need to use the multidraw extension + if(color_textures && color_textures.length > 1) + { + if( ext ) + ext.drawBuffersWEBGL( this.order ); + else + gl.drawBuffers( this.order ); + } + + //check completion + var complete = gl.checkFramebufferStatus( target ); + if(complete !== gl.FRAMEBUFFER_COMPLETE) //36054: GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT + throw("FBO not complete: " + complete); + + //restore state + gl.bindTexture(gl.TEXTURE_2D, null); + gl.bindRenderbuffer(gl.RENDERBUFFER, null); + if(!skip_disable) + gl.bindFramebuffer( target, this._old_fbo_handler ); +} + +/** +* Enables this FBO (from now on all the render will be stored in the textures attached to this FBO) +* It stores the previous viewport to restore it afterwards, and changes it to full FBO size +* @method bind +* @param {boolean} keep_old keeps the previous FBO is one was attached to restore it afterwards +*/ +FBO.prototype.bind = function( keep_old ) +{ + if(!this.color_textures.length && !this.depth_texture) + throw("FBO: no textures attached to FBO"); + this._old_viewport.set( gl.viewport_data ); + + if(keep_old) + this._old_fbo_handler = gl.getParameter( gl.FRAMEBUFFER_BINDING ); + else + this._old_fbo_handler = null; + + if(this._old_fbo_handler != this.handler ) + gl.bindFramebuffer( gl.FRAMEBUFFER, this.handler ); + + //mark them as in use in the FBO + for(var i = 0; i < this.color_textures.length; ++i) + this.color_textures[i]._in_current_fbo = true; + if(this.depth_texture) + this.depth_texture._in_current_fbo = true; + + gl.viewport( 0,0, this.width, this.height ); + FBO.current = this; +} + +/** +* Disables this FBO, if it was binded with keep_old then the old FBO is enabled, otherwise it will render to the screen +* Restores viewport to previous +* @method unbind +*/ +FBO.prototype.unbind = function() +{ + gl.bindFramebuffer( gl.FRAMEBUFFER, this._old_fbo_handler ); + this._old_fbo_handler = null; + gl.setViewport( this._old_viewport ); + + //mark the textures as no longer in use + for(var i = 0; i < this.color_textures.length; ++i) + this.color_textures[i]._in_current_fbo = false; + if(this.depth_texture) + this.depth_texture._in_current_fbo = false; + FBO.current = null; +} + +//binds another FBO without switch back to previous (faster) +FBO.prototype.switchTo = function( next_fbo ) +{ + next_fbo._old_fbo_handler = this._old_fbo_handler; + next_fbo._old_viewport.set( this._old_viewport ); + gl.bindFramebuffer( gl.FRAMEBUFFER, next_fbo.handler ); + this._old_fbo_handler = null; + gl.viewport( 0,0, this.width, this.height ); + + //mark the textures as no longer in use + for(var i = 0; i < this.color_textures.length; ++i) + this.color_textures[i]._in_current_fbo = false; + if(this.depth_texture) + this.depth_texture._in_current_fbo = false; + + //mark them as in use in the FBO + for(var i = 0; i < next_fbo.color_textures.length; ++i) + next_fbo.color_textures[i]._in_current_fbo = true; + if(next_fbo.depth_texture) + next_fbo.depth_texture._in_current_fbo = true; + + FBO.current = next_fbo; +} + +FBO.prototype.delete = function() +{ + gl.deleteFramebuffer( this.handler ); + this.handler = null; +} + +//WebGL 1.0 support for certaing FBOs is not very clear and can crash sometimes +FBO.supported = {}; +//type: gl.FLOAT, format: gl.RGBA +FBO.testSupport = function( type, format ) { + var name = type +":" + format; + if( FBO.supported[ name ] != null ) + return FBO.supported[ name ]; + + var tex = new GL.Texture(1,1,{ format: format, type: type }); + try + { + var fbo = new GL.FBO([tex]); + } + catch (err) + { + console.warn("This browser WEBGL implementation doesn't support this FBO format: " + GL.reverse[type] + " " + GL.reverse[format] ); + return FBO.supported[ name ] = false; + } + FBO.supported[ name ] = true; + return true; +} + +FBO.prototype.toSingle = function() +{ + if( this.color_textures.length < 2 ) + return; //nothing to do + var ext = gl.extensions.WEBGL_draw_buffers; + if( ext ) + ext.drawBuffersWEBGL( [ this.order[0] ] ); + else + gl.drawBuffers( [ this.order[0] ] ); +} + +FBO.prototype.toMulti = function() +{ + if( this.color_textures.length < 2 ) + return; //nothing to do + var ext = gl.extensions.WEBGL_draw_buffers; + if( ext ) + ext.drawBuffersWEBGL( this.order ); + else + gl.drawBuffers( this.order ); +} + +//clears only the secondary buffers (not the main one) +FBO.prototype.clearSecondary = function( color ) +{ + if(!this.order || this.order.length < 2) + return; + + var ext = gl.extensions.WEBGL_draw_buffers; + var new_order = [gl.NONE]; + for(var i = 1; i < this.order.length; ++i) + new_order.push(this.order[i]); + if(ext) + ext.drawBuffersWEBGL( new_order ); + else + gl.drawBuffers( new_order ); + gl.clearColor( color[0],color[1],color[2],color[3] ); + gl.clear( gl.COLOR_BUFFER_BIT ); + + if(ext) + ext.drawBuffersWEBGL( this.order ); + else + gl.drawBuffers( this.order ); +} + + + +/** +* @namespace GL +*/ + +/** +* Shader class to upload programs to the GPU +* @class Shader +* @constructor +* @param {String} vertexSource (it also allows to pass a compiled vertex shader) +* @param {String} fragmentSource (it also allows to pass a compiled fragment shader) +* @param {Object} macros (optional) precompiler macros to be applied when compiling +*/ +global.Shader = GL.Shader = function Shader( vertexSource, fragmentSource, macros ) +{ + if(GL.debug) + console.log("GL.Shader created"); + + if( !vertexSource || !fragmentSource ) + throw("GL.Shader source code parameter missing"); + + //used to avoid problems with resources moving between different webgl context + this._context_id = global.gl.context_id; + var gl = this.gl = global.gl; + + //expand macros + var extra_code = Shader.expandMacros( macros ); + + var final_vertexSource = vertexSource.constructor === String ? Shader.injectCode( extra_code, vertexSource, gl ) : vertexSource; + var final_fragmentSource = fragmentSource.constructor === String ? Shader.injectCode( extra_code, fragmentSource, gl ) : fragmentSource; + + this.program = gl.createProgram(); + + var vs = vertexSource.constructor === String ? GL.Shader.compileSource( gl.VERTEX_SHADER, final_vertexSource ) : vertexSource; + var fs = fragmentSource.constructor === String ? GL.Shader.compileSource( gl.FRAGMENT_SHADER, final_fragmentSource ) : fragmentSource; + + gl.attachShader( this.program, vs, gl ); + gl.attachShader( this.program, fs, gl ); + gl.linkProgram(this.program); + if (!gl.getProgramParameter(this.program, gl.LINK_STATUS)) { + throw 'link error: ' + gl.getProgramInfoLog(this.program); + } + + this.vs_shader = vs; + this.fs_shader = fs; + + //Extract info from the shader + this.attributes = {}; + this.uniformInfo = {}; + this.samplers = {}; + + //extract info about the shader to speed up future processes + this.extractShaderInfo(); +} + +Shader.expandMacros = function(macros) +{ + var extra_code = ""; //add here preprocessor directives that should be above everything + if(macros) + for(var i in macros) + extra_code += "#define " + i + " " + (macros[i] ? macros[i] : "") + "\n"; + return extra_code; +} + +//this is done to avoid problems with the #version which must be in the first line +Shader.injectCode = function( inject_code, code, gl ) +{ + var index = code.indexOf("\n"); + var version = ( gl ? "#define WEBGL" + gl.webgl_version + "\n" : ""); + var first_line = code.substr(0,index).trim(); + if( first_line.indexOf("#version") == -1 ) + return version + inject_code + code; + return first_line + "\n" + version + inject_code + code.substr(index); +} + + +/** +* Compiles one single shader source (could be gl.VERTEX_SHADER or gl.FRAGMENT_SHADER) and returns the webgl shader handler +* Used internaly to compile the vertex and fragment shader. +* It throws an exception if there is any error in the code +* @method Shader.compileSource +* @param {Number} type could be gl.VERTEX_SHADER or gl.FRAGMENT_SHADER +* @param {String} source the source file to compile +* @return {WebGLShader} the handler from webgl +*/ +Shader.compileSource = function( type, source, gl, shader ) +{ + gl = gl || global.gl; + shader = shader || gl.createShader(type); + gl.shaderSource(shader, source); + gl.compileShader(shader); + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { + throw (type == gl.VERTEX_SHADER ? "Vertex" : "Fragment") + ' shader compile error: ' + gl.getShaderInfoLog(shader); + } + return shader; +} + +Shader.parseError = function( error_str, vs_code, fs_code ) +{ + if(!error_str) + return null; + + var t = error_str.split(" "); + var nums = t[5].split(":"); + + return { + type: t[0], + line_number: parseInt( nums[1] ), + line_pos: parseInt( nums[0] ), + line_code: ( t[0] == "Fragment" ? fs_code : vs_code ).split("\n")[ parseInt( nums[1] ) ], + err: error_str + }; +} + +/** +* It updates the code inside one shader +* @method updateShader +* @param {String} vertexSource +* @param {String} fragmentSource +* @param {Object} macros [optional] +*/ +Shader.prototype.updateShader = function( vertexSource, fragmentSource, macros ) +{ + var gl = this.gl || global.gl; + + //expand macros + var extra_code = Shader.expandMacros( macros ); + + if(!this.program) + this.program = gl.createProgram(); + else + { + gl.detachShader( this.program, this.vs_shader ); + gl.detachShader( this.program, this.fs_shader ); + } + + var extra_code = Shader.expandMacros( macros ); + + var final_vertexSource = vertexSource.constructor === String ? Shader.injectCode( extra_code, vertexSource, gl ) : vertexSource; + var final_fragmentSource = fragmentSource.constructor === String ? Shader.injectCode( extra_code, fragmentSource, gl ) : fragmentSource; + + var vs = vertexSource.constructor === String ? GL.Shader.compileSource( gl.VERTEX_SHADER, final_vertexSource ) : vertexSource; + var fs = fragmentSource.constructor === String ? GL.Shader.compileSource( gl.FRAGMENT_SHADER, final_fragmentSource ) : fragmentSource; + + gl.attachShader( this.program, vs, gl ); + gl.attachShader( this.program, fs, gl ); + gl.linkProgram( this.program ); + if (!gl.getProgramParameter(this.program, gl.LINK_STATUS)) { + throw 'link error: ' + gl.getProgramInfoLog( this.program ); + } + + //store shaders separated + this.vs_shader = vs; + this.fs_shader = fs; + + //Extract info from the shader + this.attributes = {}; + this.uniformInfo = {}; + this.samplers = {}; + + //extract info about the shader to speed up future processes + this.extractShaderInfo(); +} + +/** +* It extract all the info about the compiled shader program, all the info about uniforms and attributes. +* This info is stored so it works faster during rendering. +* @method extractShaderInfo +*/ + + +Shader.prototype.extractShaderInfo = function() +{ + var gl = this.gl; + + var l = gl.getProgramParameter( this.program, gl.ACTIVE_UNIFORMS ); + + //extract uniforms info + for(var i = 0; i < l; ++i) + { + var data = gl.getActiveUniform( this.program, i); + if(!data) break; + + var uniformName = data.name; + + //arrays have uniformName[0], strip the [] (also data.size tells you if it is an array) + var pos = uniformName.indexOf("["); + if(pos != -1) + { + var pos2 = uniformName.indexOf("]."); //leave array of structs though + if(pos2 == -1) + uniformName = uniformName.substr(0,pos); + } + + //store texture samplers + if(data.type == gl.SAMPLER_2D || data.type == gl.SAMPLER_CUBE || data.type == GL.SAMPLER_3D) + this.samplers[ uniformName ] = data.type; + + //get which function to call when uploading this uniform + var func = Shader.getUniformFunc(data); + var is_matrix = false; + if(data.type == gl.FLOAT_MAT2 || data.type == gl.FLOAT_MAT3 || data.type == gl.FLOAT_MAT4) + is_matrix = true; + var type_length = GL.TYPE_LENGTH[ data.type ] || 1; + + //save the info so the user doesnt have to specify types when uploading data to the shader + this.uniformInfo[ uniformName ] = { + type: data.type, + func: func, + size: data.size, + type_length: type_length, + is_matrix: is_matrix, + loc: gl.getUniformLocation(this.program, uniformName), + data: new Float32Array( type_length * data.size ) //prealloc space to assign uniforms that are not typed + }; + } + + //extract attributes info + for(var i = 0, l = gl.getProgramParameter(this.program, gl.ACTIVE_ATTRIBUTES); i < l; ++i) + { + var data = gl.getActiveAttrib( this.program, i); + if(!data) + break; + var func = Shader.getUniformFunc(data); + var type_length = GL.TYPE_LENGTH[ data.type ] || 1; + this.uniformInfo[ data.name ] = { + type: data.type, + func: func, + type_length: type_length, + size: data.size, + loc: null + }; //gl.getAttribLocation( this.program, data.name ) + this.attributes[ data.name ] = gl.getAttribLocation(this.program, data.name ); + } +} + +/** +* Returns if this shader has a uniform with the given name +* @method hasUniform +* @param {String} name name of the uniform +* @return {Boolean} +*/ +Shader.prototype.hasUniform = function(name) +{ + return this.uniformInfo[name]; +} + +/** +* Returns if this shader has an attribute with the given name +* @method hasAttribute +* @param {String} name name of the attribute +* @return {Boolean} +*/ +Shader.prototype.hasAttribute = function(name) +{ + return this.attributes[name]; +} + + +/** +* Tells you which function to call when uploading a uniform according to the data type in the shader +* Used internally from extractShaderInfo to optimize calls +* @method Shader.getUniformFunc +* @param {Object} data info about the uniform +* @return {Function} +*/ +Shader.getUniformFunc = function( data ) +{ + var func = null; + switch (data.type) + { + case GL.FLOAT: + if(data.size == 1) + func = gl.uniform1f; + else + func = gl.uniform1fv; + break; + case GL.FLOAT_MAT2: func = gl.uniformMatrix2fv; break; + case GL.FLOAT_MAT3: func = gl.uniformMatrix3fv; break; + case GL.FLOAT_MAT4: func = gl.uniformMatrix4fv; break; + case GL.FLOAT_VEC2: func = gl.uniform2fv; break; + case GL.FLOAT_VEC3: func = gl.uniform3fv; break; + case GL.FLOAT_VEC4: func = gl.uniform4fv; break; + + case GL.UNSIGNED_INT: + case GL.INT: + if(data.size == 1) + func = gl.uniform1i; + else + func = gl.uniform1iv; + break; + case GL.INT_VEC2: func = gl.uniform2iv; break; + case GL.INT_VEC3: func = gl.uniform3iv; break; + case GL.INT_VEC4: func = gl.uniform4iv; break; + + case GL.SAMPLER_2D: + case GL.SAMPLER_3D: + case GL.SAMPLER_CUBE: + func = gl.uniform1i; break; + default: func = gl.uniform1f; break; + } + return func; +} + +/** +* Create a shader from two urls. While the system is fetching the two urls, the shader contains a dummy shader that renders black. +* @method Shader.fromURL +* @param {String} vs_path the url to the vertex shader +* @param {String} fs_path the url to the fragment shader +* @param {Function} on_complete [Optional] a callback to call once the shader is ready. +* @return {Shader} +*/ +Shader.fromURL = function( vs_path, fs_path, on_complete ) +{ + //create simple shader first + var vs_code = "\n\ + precision highp float;\n\ + attribute vec3 a_vertex;\n\ + attribute mat4 u_mvp;\n\ + void main() { \n\ + gl_Position = u_mvp * vec4(a_vertex,1.0); \n\ + }\n\ + "; + var fs_code = "\n\ + precision highp float;\n\ + void main() {\n\ + gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);\n\ + }\n\ + "; + + var shader = new GL.Shader(vs_code, fs_code); + shader.ready = false; + + var true_vs = null; + var true_fs = null; + + HttpRequest( vs_path, null, function(vs_code) { + true_vs = vs_code; + if(true_fs) + compileShader(); + }); + + HttpRequest( fs_path, null, function(fs_code) { + true_fs = fs_code; + if(true_vs) + compileShader(); + }); + + function compileShader() + { + var true_shader = new GL.Shader(true_vs, true_fs); + for(var i in true_shader) + shader[i] = true_shader[i]; + shader.ready = true; + } + + return shader; +} + +/** +* enables the shader (calls useProgram) +* @method bind +*/ +Shader.prototype.bind = function() +{ + var gl = this.gl; + gl.useProgram( this.program ); + gl._current_shader = this; +} + +/** +* Returns the location of a uniform or attribute +* @method getLocation +* @param {String} name +* @return {WebGLUniformLocation} location +*/ +Shader.prototype.getLocation = function( name ) +{ + var info = this.uniformInfo[name]; + if(info) + return this.uniformInfo[name].loc; + return null; +} + +/** +* Uploads a set of uniforms to the Shader. You dont need to specify types, they are infered from the shader info. +* @method uniforms +* @param {Object} uniforms +*/ +Shader._temp_uniform = new Float32Array(16); + +Shader.prototype.uniforms = function(uniforms) { + var gl = this.gl; + gl.useProgram(this.program); + gl._current_shader = this; + + for (var name in uniforms) + { + var info = this.uniformInfo[ name ]; + if (!info) + continue; + this._setUniform( name, uniforms[name] ); + //this.setUniform( name, uniforms[name] ); + //this._assing_uniform(uniforms, name, gl ); + } + + return this; +}//uniforms + +Shader.prototype.uniformsArray = function(array) { + var gl = this.gl; + gl.useProgram( this.program ); + gl._current_shader = this; + + for(var i = 0, l = array.length; i < l; ++i) + { + var uniforms = array[i]; + for (var name in uniforms) + this._setUniform( name, uniforms[name] ); + //this._assing_uniform(uniforms, name, gl ); + } + + return this; +} + +/** +* Uploads a uniform to the Shader. You dont need to specify types, they are infered from the shader info. Shader must be binded! +* @method setUniform +* @param {string} name +* @param {*} value +*/ +Shader.prototype.setUniform = (function(){ + + return (function(name, value) + { + if( this.gl._current_shader != this ) + this.bind(); + + var info = this.uniformInfo[name]; + if (!info) + return; + + if(info.loc === null) + return; + + if(value == null) //strict? + return; + + if(value.constructor === Array) + { + info.data.set( value ); + value = info.data; + } + + if(info.is_matrix) + info.func.call( this.gl, info.loc, false, value ); + else + info.func.call( this.gl, info.loc, value ); + }); +})(); + +//skips enabling shader +Shader.prototype._setUniform = (function(){ + + return (function(name, value) + { + var info = this.uniformInfo[ name ]; + if (!info) + return; + + if(info.loc === null) + return; + + //if(info.loc.constructor !== Function) + // return; + + if(value == null) + return; + + if(value.constructor === Array) + { + info.data.set( value ); + value = info.data; + } + + if(info.is_matrix) + info.func.call( this.gl, info.loc, false, value ); + else + info.func.call( this.gl, info.loc, value ); + }); +})(); + +/** +* Renders a mesh using this shader, remember to use the function uniforms before to enable the shader +* @method draw +* @param {Mesh} mesh +* @param {number} mode could be gl.LINES, gl.POINTS, gl.TRIANGLES, gl.TRIANGLE_STRIP, gl.TRIANGLE_FAN +* @param {String} index_buffer_name the name of the index buffer, if not provided triangles will be assumed +*/ +Shader.prototype.draw = function( mesh, mode, index_buffer_name ) { + index_buffer_name = index_buffer_name === undefined ? (mode == gl.LINES ? 'lines' : 'triangles') : index_buffer_name; + this.drawBuffers( mesh.vertexBuffers, + index_buffer_name ? mesh.indexBuffers[ index_buffer_name ] : null, + arguments.length < 2 ? gl.TRIANGLES : mode); +} + +/** +* Renders a range of a mesh using this shader +* @method drawRange +* @param {Mesh} mesh +* @param {number} mode could be gl.LINES, gl.POINTS, gl.TRIANGLES, gl.TRIANGLE_STRIP, gl.TRIANGLE_FAN +* @param {number} start first primitive to render +* @param {number} length number of primitives to render +* @param {String} index_buffer_name the name of the index buffer, if not provided triangles will be assumed +*/ +Shader.prototype.drawRange = function(mesh, mode, start, length, index_buffer_name ) +{ + index_buffer_name = index_buffer_name === undefined ? (mode == gl.LINES ? 'lines' : 'triangles') : index_buffer_name; + + this.drawBuffers( mesh.vertexBuffers, + index_buffer_name ? mesh.indexBuffers[ index_buffer_name ] : null, + mode, start, length); +} + +/** +* render several buffers with a given index buffer +* @method drawBuffers +* @param {Object} vertexBuffers an object containing all the buffers +* @param {IndexBuffer} indexBuffer +* @param {number} mode could be gl.LINES, gl.POINTS, gl.TRIANGLES, gl.TRIANGLE_STRIP, gl.TRIANGLE_FAN +* @param {number} range_start first primitive to render +* @param {number} range_length number of primitives to render +*/ + +//this two variables are a hack to avoid memory allocation on drawCalls +var temp_attribs_array = new Uint8Array(16); +var temp_attribs_array_zero = new Uint8Array(16); //should be filled with zeros always + +Shader.prototype.drawBuffers = function( vertexBuffers, indexBuffer, mode, range_start, range_length ) +{ + if(range_length == 0) + return; + + var gl = this.gl; + + gl.useProgram(this.program); //this could be removed assuming every shader is called with some uniforms + + // enable attributes as necessary. + var length = 0; + var attribs_in_use = temp_attribs_array; //hack to avoid garbage + attribs_in_use.set( temp_attribs_array_zero ); //reset + + for (var name in vertexBuffers) + { + var buffer = vertexBuffers[name]; + var attribute = buffer.attribute || name; + //precompute attribute locations in shader + var location = this.attributes[attribute];// || gl.getAttribLocation(this.program, attribute); + + if (location == null || !buffer.buffer) //-1 changed for null + continue; //ignore this buffer + + attribs_in_use[location] = 1; //mark it as used + + //this.attributes[attribute] = location; + gl.bindBuffer(gl.ARRAY_BUFFER, buffer.buffer); + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, buffer.buffer.spacing, buffer.buffer.gl_type, false, 0, 0); + length = buffer.buffer.length / buffer.buffer.spacing; + } + + //range rendering + var offset = 0; //in bytes + if(range_start > 0) //render a polygon range + offset = range_start; //in bytes (Uint16 == 2 bytes) + + if (indexBuffer) + length = indexBuffer.buffer.length - offset; + + if(range_length > 0 && range_length < length) //to avoid problems + length = range_length; + + var BYTES_PER_ELEMENT = (indexBuffer && indexBuffer.data) ? indexBuffer.data.constructor.BYTES_PER_ELEMENT : 1; + offset *= BYTES_PER_ELEMENT; + + // Force to disable buffers in this shader that are not in this mesh + for (var attribute in this.attributes) + { + var location = this.attributes[attribute]; + if (!(attribs_in_use[location])) { + gl.disableVertexAttribArray(this.attributes[attribute]); + } + } + + // Draw the geometry. + if (length && (!indexBuffer || indexBuffer.buffer)) { + if (indexBuffer) { + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer.buffer); + gl.drawElements( mode, length, indexBuffer.buffer.gl_type, offset); //gl.UNSIGNED_SHORT + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null); + } else { + gl.drawArrays(mode, offset, length); + } + } + + return this; +} + +Shader._instancing_arrays = []; + +Shader.prototype.drawInstanced = function( mesh, primitive, indices, instanced_uniforms, range_start, range_length, num_instances ) +{ + if(range_length === 0) + return; + + //bind buffers + var gl = this.gl; + + if( gl.webgl_version == 1 && !gl.extensions.ANGLE_instanced_arrays ) + throw("instancing not supported"); + + gl.useProgram(this.program); //this could be removed assuming every shader is called with some uniforms + + // enable attributes as necessary. + var length = 0; + var attribs_in_use = temp_attribs_array; //hack to avoid garbage + attribs_in_use.set( temp_attribs_array_zero ); //reset + + var vertexBuffers = mesh.vertexBuffers; + + for (var name in vertexBuffers) + { + var buffer = vertexBuffers[name]; + var attribute = buffer.attribute || name; + //precompute attribute locations in shader + var location = this.attributes[attribute];// || gl.getAttribLocation(this.program, attribute); + + if (location == null || !buffer.buffer) //-1 changed for null + continue; //ignore this buffer + + attribs_in_use[location] = 1; //mark it as used + + //this.attributes[attribute] = location; + gl.bindBuffer(gl.ARRAY_BUFFER, buffer.buffer); + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, buffer.buffer.spacing, buffer.buffer.gl_type, false, 0, 0); + length = buffer.buffer.length / buffer.buffer.spacing; + } + + var indexBuffer = null; + if(indices) + { + if(indices.constructor === GL.Buffer) + indexBuffer = indices; + else + indexBuffer = mesh.getIndexBuffer( indices ); + } + + //range rendering + var offset = 0; //in bytes + if(range_start > 0) //render a polygon range + offset = range_start; + + if (indexBuffer) + length = indexBuffer.buffer.length - offset; + + if(range_length > 0 && range_length < length) //to avoid problems + length = range_length; + + var BYTES_PER_ELEMENT = (indexBuffer && indexBuffer.data) ? indexBuffer.data.constructor.BYTES_PER_ELEMENT : 1; + offset *= BYTES_PER_ELEMENT; + + // Force to disable buffers in this shader that are not in this mesh + for (var attribute in this.attributes) + { + var location = this.attributes[attribute]; + if (!(attribs_in_use[location])) { + gl.disableVertexAttribArray(this.attributes[attribute]); + } + } + + var ext = gl.extensions.ANGLE_instanced_arrays; + var batch_length = 0; + + //pack the instanced uniforms + var index = 0; + for(var uniform in instanced_uniforms) + { + var values = instanced_uniforms[ uniform ]; + batch_length = values.length; + var uniformLocation = this.attributes[ uniform ]; + if( uniformLocation == null ) + return; //not found + var element_size = 0; + var total_size = 0; + if( values.constructor === Array ) + { + element_size = values[0].constructor === Number ? 1 : values[0].length; + total_size = element_size * values.length; + } + else //typed array + { + element_size = this.uniformInfo[ uniform ].type_length; + total_size = values.length; + batch_length = total_size / element_size; + } + + var data_array = Shader._instancing_arrays[ index ]; + if( !data_array || data_array.data.length < total_size ) + data_array = Shader._instancing_arrays[ index ] = { data: new Float32Array( total_size ), buffer: gl.createBuffer() }; + data_array.uniform = uniform; + data_array.element_size = element_size; + if( values.constructor === Array ) + for(var j = 0; j < values.length; ++j) + data_array.data.set( values[j], j*element_size ); //flatten array + else + data_array.data.set( values ); //copy + gl.bindBuffer( gl.ARRAY_BUFFER, data_array.buffer ); + gl.bufferData( gl.ARRAY_BUFFER, data_array.data, gl.STREAM_DRAW ); + + if(element_size == 16) //mat4 + { + for(var k = 0; k < 4; ++k) + { + gl.enableVertexAttribArray( uniformLocation+k ); + gl.vertexAttribPointer( uniformLocation+k, 4, gl.FLOAT , false, 16*4, k*4*4 ); //4 bytes per float + if( ext ) //webgl 1 + ext.vertexAttribDivisorANGLE( uniformLocation+k, 1 ); // This makes it instanced! + else + gl.vertexAttribDivisor( uniformLocation+k, 1 ); // This makes it instanced! + } + } + else //others + { + gl.enableVertexAttribArray( uniformLocation ); + gl.vertexAttribPointer( uniformLocation, element_size, gl.FLOAT, false, element_size*4, 0 ); //4 bytes per float, 0 offset + if( ext ) //webgl 1 + ext.vertexAttribDivisorANGLE( uniformLocation, 1 ); // This makes it instanced! + else + gl.vertexAttribDivisor( uniformLocation, 1 ); // This makes it instanced! + } + index+=1; + } + + if( num_instances ) + batch_length = num_instances; + + if( ext ) //webgl 1.0 + { + if(indexBuffer) + { + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer.buffer); + ext.drawElementsInstancedANGLE( primitive, length, indexBuffer.buffer.gl_type, offset, batch_length ); + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null ); + } + else + ext.drawArraysInstancedANGLE( primitive, offset, length, batch_length); + } + else + { + if(indexBuffer) + { + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer.buffer); + gl.drawElementsInstanced( primitive, length, indexBuffer.buffer.gl_type, offset, batch_length ); + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null ); + } + else + gl.drawArraysInstanced( primitive, offset, length, batch_length); + } + + //disable instancing buffers + for(var i = 0; i < index; ++i) + { + var info = Shader._instancing_arrays[ i ]; + var uniformLocation = this.attributes[ info.uniform ]; + var element_size = info.element_size; + if( element_size == 16) //mat4 + { + for(var k = 0; k < 4; ++k) + { + gl.disableVertexAttribArray( uniformLocation+k ); + if( ext ) //webgl 1 + ext.vertexAttribDivisorANGLE( uniformLocation+k, 0 ); + else + gl.vertexAttribDivisor( uniformLocation+k, 0 ); + } + } + else //others + { + gl.enableVertexAttribArray( uniformLocation ); + if( ext ) //webgl 1 + ext.vertexAttribDivisorANGLE( uniformLocation, 0 ); + else + gl.vertexAttribDivisor( uniformLocation, 0 ); + } + } + + return this; +} + + + +/** +* Given a source code with the directive #import it expands it inserting the code using Shader.files to fetch for import files. +* Warning: Imports are evaluated only the first inclusion, the rest are ignored to avoid double inclusion of functions +* Also, imports cannot have other imports inside. +* @method Shader.expandImports +* @param {String} code the source code +* @param {Object} files [Optional] object with files to import from (otherwise Shader.files is used) +* @return {String} the code with the lines #import removed and replaced by the code +*/ +Shader.expandImports = function(code, files) +{ + files = files || Shader.files; + + var already_imported = {}; //avoid to import two times the same code + if( !files ) + throw("Shader.files not initialized, assign files there"); + + var replace_import = function(v) + { + var token = v.split("\""); + var id = token[1]; + if( already_imported[id] ) + return "//already imported: " + id + "\n"; + var file = files[id]; + already_imported[ id ] = true; + if(file) + return file + "\n"; + return "//import code not found: " + id + "\n"; + } + + //return code.replace(/#import\s+\"(\w+)\"\s*\n/g, replace_import ); + return code.replace(/#import\s+\"([a-zA-Z0-9_\.]+)\"\s*\n/g, replace_import ); +} + +Shader.dumpErrorToConsole = function(err, vscode, fscode) +{ + console.error(err); + var msg = err.msg; + var code = null; + if(err.indexOf("Fragment") != -1) + code = fscode; + else + code = vscode; + + var lines = code.split("\n"); + for(var i in lines) + lines[i] = i + "| " + lines[i]; + + console.groupCollapsed("Shader code"); + console.log( lines.join("\n") ); + console.groupEnd(); +} + +Shader.convertTo100 = function(code,type) +{ + //in VERTEX + //change in for attribute + //change out for varying + //add #extension GL_OES_standard_derivatives + //in FRAGMENT + //change in for varying + //remove out vec4 _gl_FragColor + //rename _gl_FragColor for gl_FragColor + //in both + //change #version 300 es for #version 100 + //replace 'texture(' for 'texture2D(' +} + + +Shader.convertTo300 = function(code,type) +{ + //in VERTEX + //change attribute for in + //change varying for out + //remove #extension GL_OES_standard_derivatives + //in FRAGMENT + //change varying for in + //rename gl_FragColor for _gl_FragColor + //rename gl_FragData[0] for _gl_FragColor + //add out vec4 _gl_FragColor + //in both + //replace texture2D for texture +} + +//helps to check if a variable value is valid to an specific uniform in a shader +Shader.validateValue = function( value, uniform_info ) +{ + if(value === null || value === undefined) + return false; + + switch (uniform_info.type) + { + //used to validate shaders + case GL.INT: + case GL.FLOAT: + case GL.SAMPLER_2D: + case GL.SAMPLER_CUBE: + return isNumber(value); + case GL.INT_VEC2: + case GL.FLOAT_VEC2: + return value.length === 2; + case GL.INT_VEC3: + case GL.FLOAT_VEC3: + return value.length === 3; + case GL.INT_VEC4: + case GL.FLOAT_VEC4: + case GL.FLOAT_MAT2: + return value.length === 4; + case GL.FLOAT_MAT3: + return value.length === 8; + case GL.FLOAT_MAT4: + return value.length === 16; + } + return true; +} + +//**************** SHADERS *********************************** + +Shader.DEFAULT_VERTEX_SHADER = "\n\ + precision highp float;\n\ + attribute vec3 a_vertex;\n\ + attribute vec3 a_normal;\n\ + attribute vec2 a_coord;\n\ + varying vec3 v_position;\n\ + varying vec3 v_normal;\n\ + varying vec2 v_coord;\n\ + uniform mat4 u_model;\n\ + uniform mat4 u_mvp;\n\ + void main() {\n\ + v_position = (u_model * vec4(a_vertex,1.0)).xyz;\n\ + v_normal = (u_model * vec4(a_normal,0.0)).xyz;\n\ + v_coord = a_coord;\n\ + gl_Position = u_mvp * vec4(a_vertex,1.0);\n\ + }\n\ + "; + +Shader.SCREEN_VERTEX_SHADER = "\n\ + precision highp float;\n\ + attribute vec3 a_vertex;\n\ + attribute vec2 a_coord;\n\ + varying vec2 v_coord;\n\ + void main() { \n\ + v_coord = a_coord; \n\ + gl_Position = vec4(a_coord * 2.0 - 1.0, 0.0, 1.0); \n\ + }\n\ + "; + +Shader.SCREEN_FRAGMENT_SHADER = "\n\ + precision highp float;\n\ + uniform sampler2D u_texture;\n\ + varying vec2 v_coord;\n\ + void main() {\n\ + gl_FragColor = texture2D(u_texture, v_coord);\n\ + }\n\ + "; + +//used in createFX +Shader.SCREEN_FRAGMENT_FX = "\n\ + precision highp float;\n\ + uniform sampler2D u_texture;\n\ + varying vec2 v_coord;\n\ + #ifdef FX_UNIFORMS\n\ + FX_UNIFORMS\n\ + #endif\n\ + void main() {\n\ + vec2 uv = v_coord;\n\ + vec4 color = texture2D(u_texture, uv);\n\ + #ifdef FX_CODE\n\ + FX_CODE ;\n\ + #endif\n\ + gl_FragColor = color;\n\ + }\n\ + "; + +Shader.SCREEN_COLORED_FRAGMENT_SHADER = "\n\ + precision highp float;\n\ + uniform sampler2D u_texture;\n\ + uniform vec4 u_color;\n\ + varying vec2 v_coord;\n\ + void main() {\n\ + gl_FragColor = u_color * texture2D(u_texture, v_coord);\n\ + }\n\ + "; + +Shader.BLEND_FRAGMENT_SHADER = "\n\ + precision highp float;\n\ + uniform sampler2D u_texture;\n\ + uniform sampler2D u_texture2;\n\ + uniform float u_factor;\n\ + varying vec2 v_coord;\n\ + void main() {\n\ + gl_FragColor = mix( texture2D(u_texture, v_coord), texture2D(u_texture2, v_coord), u_factor);\n\ + }\n\ + "; + +//used to paint quads +Shader.QUAD_VERTEX_SHADER = "\n\ + precision highp float;\n\ + attribute vec3 a_vertex;\n\ + attribute vec2 a_coord;\n\ + varying vec2 v_coord;\n\ + uniform vec2 u_position;\n\ + uniform vec2 u_size;\n\ + uniform vec2 u_viewport;\n\ + uniform mat3 u_transform;\n\ + void main() { \n\ + vec3 pos = vec3(u_position + vec2(a_coord.x,1.0 - a_coord.y) * u_size, 1.0);\n\ + v_coord = a_coord; \n\ + pos = u_transform * pos;\n\ + pos.z = 0.0;\n\ + //normalize\n\ + pos.x = (2.0 * pos.x / u_viewport.x) - 1.0;\n\ + pos.y = -((2.0 * pos.y / u_viewport.y) - 1.0);\n\ + gl_Position = vec4(pos, 1.0); \n\ + }\n\ + "; + +Shader.QUAD_FRAGMENT_SHADER = "\n\ + precision highp float;\n\ + uniform sampler2D u_texture;\n\ + uniform vec4 u_color;\n\ + varying vec2 v_coord;\n\ + void main() {\n\ + gl_FragColor = u_color * texture2D(u_texture, v_coord);\n\ + }\n\ + "; + +//used to render partially a texture +Shader.QUAD2_FRAGMENT_SHADER = "\n\ + precision highp float;\n\ + uniform sampler2D u_texture;\n\ + uniform vec4 u_color;\n\ + uniform vec4 u_texture_area;\n\ + varying vec2 v_coord;\n\ + void main() {\n\ + vec2 uv = vec2( mix(u_texture_area.x, u_texture_area.z, v_coord.x), 1.0 - mix(u_texture_area.w, u_texture_area.y, v_coord.y) );\n\ + gl_FragColor = u_color * texture2D(u_texture, uv);\n\ + }\n\ + "; + +Shader.PRIMITIVE2D_VERTEX_SHADER = "\n\ + precision highp float;\n\ + attribute vec3 a_vertex;\n\ + uniform vec2 u_viewport;\n\ + uniform mat3 u_transform;\n\ + void main() { \n\ + vec3 pos = a_vertex;\n\ + pos = u_transform * pos;\n\ + pos.z = 0.0;\n\ + //normalize\n\ + pos.x = (2.0 * pos.x / u_viewport.x) - 1.0;\n\ + pos.y = -((2.0 * pos.y / u_viewport.y) - 1.0);\n\ + gl_Position = vec4(pos, 1.0); \n\ + }\n\ + "; + +Shader.FLAT_VERTEX_SHADER = "\n\ + precision highp float;\n\ + attribute vec3 a_vertex;\n\ + uniform mat4 u_mvp;\n\ + void main() { \n\ + gl_Position = u_mvp * vec4(a_vertex,1.0); \n\ + }\n\ + "; + +Shader.FLAT_FRAGMENT_SHADER = "\n\ + precision highp float;\n\ + uniform vec4 u_color;\n\ + void main() {\n\ + gl_FragColor = u_color;\n\ + }\n\ + "; +Shader.SCREEN_FLAT_FRAGMENT_SHADER = Shader.FLAT_FRAGMENT_SHADER; //legacy + + +/** +* Allows to create a simple shader meant to be used to process a texture, instead of having to define the generic Vertex & Fragment Shader code +* @method Shader.createFX +* @param {string} code string containg code, like "color = color * 2.0;" +* @param {string} [uniforms=null] string containg extra uniforms, like "uniform vec3 u_pos;" +*/ +Shader.createFX = function(code, uniforms, shader) +{ + //remove comments + code = GL.Shader.removeComments( code, true ); //remove comments and breaklines to avoid problems with the macros + var macros = { + FX_CODE: code, + FX_UNIFORMS: uniforms || "" + } + if(!shader) + return new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, GL.Shader.SCREEN_FRAGMENT_FX, macros ); + shader.updateShader( GL.Shader.SCREEN_VERTEX_SHADER, GL.Shader.SCREEN_FRAGMENT_FX, macros ); + return shader; +} + +/** +* Given a shader code with some vars inside (like {{varname}}) and an object with the variable values, it will replace them. +* @method Shader.replaceCodeUsingContext +* @param {string} code string containg code and vars in {{varname}} format +* @param {object} context object containing all var values +*/ +Shader.replaceCodeUsingContext = function( code_template, context ) +{ + return code_template.replace(/\{\{[a-zA-Z0-9_]*\}\}/g, function(v){ + v = v.replace( /[\{\}]/g, "" ); + return context[v] || ""; + }); +} + +Shader.removeComments = function(code, one_line) +{ + if(!code) + return ""; + + var rx = /(\/\*([^*]|[\r\n]|(\*+([^*\/]|[\r\n])))*\*+\/)|(\/\/.*)/g; + var code = code.replace( rx ,""); + var lines = code.split("\n"); + var result = []; + for(var i = 0; i < lines.length; ++i) + { + var line = lines[i]; + var pos = line.indexOf("//"); + if(pos != -1) + line = lines[i].substr(0,pos); + line = line.trim(); + if(line.length) + result.push(line); + } + return result.join( one_line ? "" : "\n" ); +} + +/** +* Renders a fullscreen quad with this shader applied +* @method toViewport +* @param {object} uniforms +*/ +Shader.prototype.toViewport = function(uniforms) +{ + var mesh = GL.Mesh.getScreenQuad(); + if(uniforms) + this.uniforms(uniforms); + this.draw( mesh ); +} + +//Now some common shaders everybody needs + +/** +* Returns a shader ready to render a textured quad in fullscreen, use with Mesh.getScreenQuad() mesh +* shader params: sampler2D u_texture +* @method Shader.getScreenShader +*/ +Shader.getScreenShader = function(gl) +{ + gl = gl || global.gl; + var shader = gl.shaders[":screen"]; + if(shader) + return shader; + shader = gl.shaders[":screen"] = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, Shader.SCREEN_FRAGMENT_SHADER ); + return shader.uniforms({u_texture:0}); //do it the first time so I dont have to do it every time +} + +/** +* Returns a shader ready to render a flat color quad in fullscreen, use with Mesh.getScreenQuad() mesh +* shader params: vec4 u_color +* @method Shader.getFlatScreenShader +*/ +Shader.getFlatScreenShader = function(gl) +{ + gl = gl || global.gl; + var shader = gl.shaders[":flat_screen"]; + if(shader) + return shader; + shader = gl.shaders[":flat_screen"] = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, Shader.FLAT_FRAGMENT_SHADER ); + return shader.uniforms({u_color:[1,1,1,1]}); //do it the first time so I dont have to do it every time +} + +/** +* Returns a shader ready to render a colored textured quad in fullscreen, use with Mesh.getScreenQuad() mesh +* shader params vec4 u_color and sampler2D u_texture +* @method Shader.getColoredScreenShader +*/ +Shader.getColoredScreenShader = function(gl) +{ + gl = gl || global.gl; + var shader = gl.shaders[":colored_screen"]; + if(shader) + return shader; + shader = gl.shaders[":colored_screen"] = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, Shader.SCREEN_COLORED_FRAGMENT_SHADER ); + return shader.uniforms({u_texture:0, u_color: vec4.fromValues(1,1,1,1) }); //do it the first time so I dont have to do it every time +} + +/** +* Returns a shader ready to render a quad with transform, use with Mesh.getScreenQuad() mesh +* shader must have: u_position, u_size, u_viewport, u_transform (mat3) +* @method Shader.getQuadShader +*/ +Shader.getQuadShader = function(gl) +{ + gl = gl || global.gl; + var shader = gl.shaders[":quad"]; + if(shader) + return shader; + return gl.shaders[":quad"] = new GL.Shader( Shader.QUAD_VERTEX_SHADER, Shader.QUAD_FRAGMENT_SHADER ); +} + +/** +* Returns a shader ready to render part of a texture into the viewport +* shader must have: u_position, u_size, u_viewport, u_transform, u_texture_area (vec4) +* @method Shader.getPartialQuadShader +*/ +Shader.getPartialQuadShader = function(gl) +{ + gl = gl || global.gl; + var shader = gl.shaders[":quad2"]; + if(shader) + return shader; + return gl.shaders[":quad2"] = new GL.Shader( Shader.QUAD_VERTEX_SHADER, Shader.QUAD2_FRAGMENT_SHADER ); +} + +/** +* Returns a shader that blends two textures +* shader must have: u_factor, u_texture, u_texture2 +* @method Shader.getBlendShader +*/ +Shader.getBlendShader = function(gl) +{ + gl = gl || global.gl; + var shader = gl.shaders[":blend"]; + if(shader) + return shader; + return gl.shaders[":blend"] = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, Shader.BLEND_FRAGMENT_SHADER ); +} + +/** +* Returns a shader used to apply gaussian blur to one texture in one axis (you should use it twice to get a gaussian blur) +* shader params are: vec2 u_offset, float u_intensity +* @method Shader.getBlurShader +*/ +Shader.getBlurShader = function(gl) +{ + gl = gl || global.gl; + var shader = gl.shaders[":blur"]; + if(shader) + return shader; + + var shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER,"\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\ + 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 += texture2D(u_texture, v_coord) * 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\ + "); + return gl.shaders[":blur"] = shader; +} + +//shader to copy a depth texture into another one +Shader.getCopyDepthShader = function(gl) +{ + gl = gl || global.gl; + var shader = gl.shaders[":copy_depth"]; + if(shader) + return shader; + + var shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER,"\n\ + #extension GL_EXT_frag_depth : enable\n\ + precision highp float;\n\ + varying vec2 v_coord;\n\ + uniform sampler2D u_texture;\n\ + void main() {\n\ + gl_FragDepthEXT = texture2D( u_texture, v_coord ).x;\n\ + gl_FragColor = vec4(1.0);\n\ + }\n\ + "); + return gl.shaders[":copy_depth"] = shader; +} + +Shader.getCubemapShowShader = function(gl) +{ + gl = gl || global.gl; + var shader = gl.shaders[":show_cubemap"]; + if(shader) + return shader; + + var shader = new GL.Shader( Shader.DEFAULT_VERTEX_SHADER,"\n\ + precision highp float;\n\ + varying vec3 v_normal;\n\ + uniform samplerCube u_texture;\n\ + void main() {\n\ + gl_FragColor = textureCube( u_texture, v_normal );\n\ + }\n\ + "); + shader.uniforms({u_texture:0}); + return gl.shaders[":show_cubemap"] = shader; +} + +//shader to copy a cubemap into another +Shader.getPolarToCubemapShader = function(gl) +{ + gl = gl || global.gl; + var shader = gl.shaders[":polar_to_cubemap"]; + if(shader) + return shader; + + var shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER,"\n\ + precision highp float;\n\ + varying vec2 v_coord;\n\ + uniform sampler2D u_texture;\n\ + uniform mat3 u_rotation;\n\ + void main() {\n\ + vec2 uv = vec2( v_coord.x, 1.0 - v_coord.y );\n\ + vec3 dir = normalize( vec3( uv - vec2(0.5), 0.5 ));\n\ + dir = u_rotation * dir;\n\ + float u = atan(dir.x,dir.z) / 6.28318531;\n\ + float v = (asin(dir.y) / 1.57079633) * 0.5 + 0.5;\n\ + u = mod(u,1.0);\n\ + v = mod(v,1.0);\n\ + gl_FragColor = texture2D( u_texture, vec2(u,v) );\n\ + }\n\ + "); + return gl.shaders[":polar_to_cubemap"] = shader; +} + + +//shader to copy a cubemap into another +Shader.getCubemapCopyShader = function(gl) +{ + gl = gl || global.gl; + var shader = gl.shaders[":copy_cubemap"]; + if(shader) + return shader; + + var shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER,"\n\ + precision highp float;\n\ + varying vec2 v_coord;\n\ + uniform samplerCube u_texture;\n\ + uniform mat3 u_rotation;\n\ + void main() {\n\ + vec2 uv = vec2( v_coord.x, 1.0 - v_coord.y );\n\ + vec3 dir = vec3( uv - vec2(0.5), 0.5 );\n\ + dir = u_rotation * dir;\n\ + gl_FragColor = textureCube( u_texture, dir );\n\ + }\n\ + "); + return gl.shaders[":copy_cubemap"] = shader; +} + +//shader to blur a cubemap +Shader.getCubemapBlurShader = function(gl) +{ + gl = gl || global.gl; + var shader = gl.shaders[":blur_cubemap"]; + if(shader) + return shader; + + var shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER,"\n\ + #ifndef NUM_SAMPLES\n\ + #define NUM_SAMPLES 4\n\ + #endif\n\ + \n\ + precision highp float;\n\ + varying vec2 v_coord;\n\ + uniform samplerCube u_texture;\n\ + uniform mat3 u_rotation;\n\ + uniform vec2 u_offset;\n\ + uniform float u_intensity;\n\ + void main() {\n\ + vec4 sum = vec4(0.0);\n\ + vec2 uv = vec2( v_coord.x, 1.0 - v_coord.y ) - vec2(0.5);\n\ + vec3 dir = vec3(0.0);\n\ + vec4 color = vec4(0.0);\n\ + for( int x = -2; x <= 2; x++ )\n\ + {\n\ + for( int y = -2; y <= 2; y++ )\n\ + {\n\ + dir.xy = uv + vec2( u_offset.x * float(x), u_offset.y * float(y)) * 0.5;\n\ + dir.z = 0.5;\n\ + dir = u_rotation * dir;\n\ + color = textureCube( u_texture, dir );\n\ + color.xyz = color.xyz * color.xyz;/*linearize*/\n\ + sum += color;\n\ + }\n\ + }\n\ + sum /= 25.0;\n\ + gl_FragColor = vec4( sqrt( sum.xyz ), sum.w ) ;\n\ + }\n\ + "); + return gl.shaders[":blur_cubemap"] = shader; +} + +//shader to do FXAA (antialiasing) +Shader.FXAA_FUNC = "\n\ + uniform vec2 u_viewportSize;\n\ + uniform vec2 u_iViewportSize;\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 u_iViewportSize = vec2(1.0 / u_viewportSize.x, 1.0 / u_viewportSize.y);*/\n\ + vec3 rgbNW = texture2D(tex, (fragCoord + vec2(-1.0, -1.0)) * u_iViewportSize).xyz;\n\ + vec3 rgbNE = texture2D(tex, (fragCoord + vec2(1.0, -1.0)) * u_iViewportSize).xyz;\n\ + vec3 rgbSW = texture2D(tex, (fragCoord + vec2(-1.0, 1.0)) * u_iViewportSize).xyz;\n\ + vec3 rgbSE = texture2D(tex, (fragCoord + vec2(1.0, 1.0)) * u_iViewportSize).xyz;\n\ + vec3 rgbM = texture2D(tex, fragCoord * u_iViewportSize).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)) * u_iViewportSize;\n\ + \n\ + vec3 rgbA = 0.5 * (texture2D(tex, fragCoord * u_iViewportSize + dir * (1.0 / 3.0 - 0.5)).xyz + \n\ + texture2D(tex, fragCoord * u_iViewportSize + dir * (2.0 / 3.0 - 0.5)).xyz);\n\ + vec3 rgbB = rgbA * 0.5 + 0.25 * (texture2D(tex, fragCoord * u_iViewportSize + dir * -0.5).xyz + \n\ + texture2D(tex, fragCoord * u_iViewportSize + 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\ + return color;\n\ + }\n\ +"; + +/** +* Returns a shader to apply FXAA antialiasing +* params are vec2 u_viewportSize, vec2 u_iViewportSize or you can call shader.setup() +* @method Shader.getFXAAShader +*/ +Shader.getFXAAShader = function(gl) +{ + gl = gl || global.gl; + var shader = gl.shaders[":fxaa"]; + if(shader) + return shader; + + var shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER,"\n\ + precision highp float;\n\ + varying vec2 v_coord;\n\ + uniform sampler2D u_texture;\n\ + " + Shader.FXAA_FUNC + "\n\ + \n\ + void main() {\n\ + gl_FragColor = applyFXAA( u_texture, v_coord * u_viewportSize) ;\n\ + }\n\ + "); + + var viewport = vec2.fromValues( gl.viewport_data[2], gl.viewport_data[3] ); + var iviewport = vec2.fromValues( 1/gl.viewport_data[2], 1/gl.viewport_data[3] ); + + shader.setup = function() { + viewport[0] = gl.viewport_data[2]; + viewport[1] = gl.viewport_data[3]; + iviewport[0] = 1/gl.viewport_data[2]; + iviewport[1] = 1/gl.viewport_data[3]; + this.uniforms({ u_viewportSize: viewport, u_iViewportSize: iviewport }); + } + return gl.shaders[":fxaa"] = shader; +} + +/** +* Returns a flat shader (useful to render lines) +* @method Shader.getFlatShader +*/ +Shader.getFlatShader = function(gl) +{ + gl = gl || global.gl; + var shader = gl.shaders[":flat"]; + if(shader) + return shader; + + var shader = new GL.Shader( Shader.FLAT_VERTEX_SHADER,Shader.FLAT_FRAGMENT_SHADER); + shader.uniforms({u_color:[1,1,1,1]}); + return gl.shaders[":flat"] = shader; +} + +/** +* The global scope that contains all the classes from LiteGL and also all the enums of WebGL so you dont need to create a context to use the values. +* @class GL +*/ + +/** +* creates a new WebGL context (it can create the canvas or use an existing one) +* @method create +* @param {Object} options supported are: +* - width +* - height +* - canvas +* - container (string or element) +* @return {WebGLRenderingContext} webgl context with all the extra functions (check gl in the doc for more info) +*/ +GL.create = function(options) { + options = options || {}; + var canvas = null; + if(options.canvas) + { + if(typeof(options.canvas) == "string") + { + canvas = document.getElementById( options.canvas ); + if(!canvas) throw("Canvas element not found: " + options.canvas ); + } + else + canvas = options.canvas; + } + else + { + var root = null; + if(options.container) + root = options.container.constructor === String ? document.querySelector( options.container ) : options.container; + if(root && !options.width) + { + var rect = root.getBoundingClientRect(); + options.width = rect.width; + options.height = rect.height; + } + + canvas = createCanvas( options.width || 800, options.height || 600 ); + if(root) + root.appendChild(canvas); + } + + if (!('alpha' in options)) options.alpha = false; + + + /** + * the webgl context returned by GL.create, its a WebGLRenderingContext with some extra methods added + * @class gl + */ + var gl = null; + + var seq = null; + if(options.version == 2) + seq = ['webgl2','experimental-webgl2']; + else if(options.version == 1 || options.version === undefined) //default + seq = ['webgl','experimental-webgl']; + else if(options.version === 0) //latest + seq = ['webgl2','experimental-webgl2','webgl','experimental-webgl']; + + if(!seq) + throw 'Incorrect WebGL version, must be 1 or 2'; + + var context_options = { + alpha: options.alpha === undefined ? true : options.alpha, + depth: options.depth === undefined ? true : options.depth, + stencil: options.stencil === undefined ? true : options.stencil, + antialias: options.antialias === undefined ? true : options.antialias, + premultipliedAlpha: options.premultipliedAlpha === undefined ? true : options.premultipliedAlpha, + preserveDrawingBuffer: options.preserveDrawingBuffer === undefined ? true : options.preserveDrawingBuffer + }; + + for(var i = 0; i < seq.length; ++i) + { + try { gl = canvas.getContext( seq[i], context_options ); } catch (e) {} + if(gl) + break; + } + + if (!gl) + { + if( canvas.getContext( "webgl" ) ) + throw 'WebGL supported but not with those parameters'; + throw 'WebGL not supported'; + } + + //context globals + gl.webgl_version = gl.constructor.name === "WebGL2RenderingContext" ? 2 : 1; + global.gl = gl; + canvas.is_webgl = true; + canvas.gl = gl; + gl.context_id = this.last_context_id++; + + //get all supported extensions + var supported_extensions = gl.getSupportedExtensions(); + gl.extensions = {}; + for(var i in supported_extensions) + gl.extensions[ supported_extensions[i] ] = gl.getExtension( supported_extensions[i] ); + gl.derivatives_supported = gl.extensions['OES_standard_derivatives'] != null || gl.webgl_version > 1; + + /* + gl.extensions["OES_standard_derivatives"] = gl.derivatives_supported = gl.getExtension('OES_standard_derivatives') || false; + gl.extensions["WEBGL_depth_texture"] = gl.getExtension("WEBGL_depth_texture") || gl.getExtension("WEBKIT_WEBGL_depth_texture") || gl.getExtension("MOZ_WEBGL_depth_texture"); + gl.extensions["OES_element_index_uint"] = gl.getExtension("OES_element_index_uint"); + gl.extensions["WEBGL_draw_buffers"] = gl.getExtension("WEBGL_draw_buffers"); + gl.extensions["EXT_shader_texture_lod"] = gl.getExtension("EXT_shader_texture_lod"); + gl.extensions["EXT_sRGB"] = gl.getExtension("EXT_sRGB"); + gl.extensions["EXT_texture_filter_anisotropic"] = gl.getExtension("EXT_texture_filter_anisotropic") || gl.getExtension("WEBKIT_EXT_texture_filter_anisotropic") || gl.getExtension("MOZ_EXT_texture_filter_anisotropic"); + gl.extensions["EXT_frag_depth"] = gl.getExtension("EXT_frag_depth") || gl.getExtension("WEBKIT_EXT_frag_depth") || gl.getExtension("MOZ_EXT_frag_depth"); + gl.extensions["WEBGL_lose_context"] = gl.getExtension("WEBGL_lose_context") || gl.getExtension("WEBKIT_WEBGL_lose_context") || gl.getExtension("MOZ_WEBGL_lose_context"); + gl.extensions["ANGLE_instanced_arrays"] = gl.getExtension("ANGLE_instanced_arrays"); + gl.extensions["disjoint_timer_query"] = gl.getExtension("EXT_disjoint_timer_query"); + + //for float textures + gl.extensions["OES_texture_float_linear"] = gl.getExtension("OES_texture_float_linear"); + if(gl.extensions["OES_texture_float_linear"]) + gl.extensions["OES_texture_float"] = gl.getExtension("OES_texture_float"); + gl.extensions["EXT_color_buffer_float"] = gl.getExtension("EXT_color_buffer_float"); + + //for half float textures in webgl 1 require extension + gl.extensions["OES_texture_half_float_linear"] = gl.getExtension("OES_texture_half_float_linear"); + if(gl.extensions["OES_texture_half_float_linear"]) + gl.extensions["OES_texture_half_float"] = gl.getExtension("OES_texture_half_float"); + */ + + if( gl.webgl_version == 1 ) + gl.HIGH_PRECISION_FORMAT = gl.extensions["OES_texture_half_float"] ? GL.HALF_FLOAT_OES : (gl.extensions["OES_texture_float"] ? GL.FLOAT : GL.UNSIGNED_BYTE); //because Firefox dont support half float + else + gl.HIGH_PRECISION_FORMAT = GL.HALF_FLOAT_OES; + + gl.max_texture_units = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); + + //viewport hack to retrieve it without using getParameter (which is slow and generates garbage) + if(!gl._viewport_func) + { + gl._viewport_func = gl.viewport; + gl.viewport_data = new Float32Array([0,0,gl.canvas.width,gl.canvas.height]); //32000 max viewport, I guess its fine + gl.viewport = function(a,b,c,d) { var v = this.viewport_data; v[0] = a|0; v[1] = b|0; v[2] = c|0; v[3] = d|0; this._viewport_func(a,b,c,d); } + gl.getViewport = function(v) { + if(v) { v[0] = gl.viewport_data[0]; v[1] = gl.viewport_data[1]; v[2] = gl.viewport_data[2]; v[3] = gl.viewport_data[3]; return v; } + return new Float32Array( gl.viewport_data ); + }; + gl.setViewport = function( v, flip_y ) { + gl.viewport_data.set(v); + if(flip_y) + gl.viewport_data[1] = this.drawingBufferHeight-v[1]-v[3]; + this._viewport_func(v[0],gl.viewport_data[1],v[2],v[3]); + }; + } + else + console.warn("Creating LiteGL context over the same canvas twice"); + + //reverse names helper (assuming no names repeated) + if(!GL.reverse) + { + GL.reverse = {}; + for(var i in gl) + if( gl[i] && gl[i].constructor === Number ) + GL.reverse[ gl[i] ] = i; + } + + //just some checks + if(typeof(glMatrix) == "undefined") + throw("glMatrix not found, LiteGL requires glMatrix to be included"); + + var last_click_time = 0; + + //some global containers, use them to reuse assets + gl.shaders = {}; + gl.textures = {}; + gl.meshes = {}; + + /** + * sets this context as the current global gl context (in case you have more than one) + * @method makeCurrent + */ + gl.makeCurrent = function() + { + global.gl = this; + } + + /** + * executes callback inside this webgl context + * @method execute + * @param {Function} callback + */ + gl.execute = function(callback) + { + var old_gl = global.gl; + global.gl = this; + callback(); + global.gl = old_gl; + } + + + /** + * Launch animation loop (calls gl.onupdate and gl.ondraw every frame) + * example: gl.ondraw = function(){ ... } or gl.onupdate = function(dt) { ... } + * @method animate + */ + gl.animate = function(v) { + if(v === false) + { + global.cancelAnimationFrame( this._requestFrame_id ); + this._requestFrame_id = null; + return; + } + + var post = global.requestAnimationFrame; + var time = getTime(); + var context = this; + + //loop only if browser tab visible + function loop() { + if(gl.destroyed) //to stop rendering once it is destroyed + return; + + context._requestFrame_id = post(loop); //do it first, in case it crashes + + var now = getTime(); + var dt = (now - time) * 0.001; + if(context.mouse) + context.mouse.last_buttons = context.mouse.buttons; + if (context.onupdate) + context.onupdate(dt); + LEvent.trigger( context, "update", dt); + if (context.ondraw) + { + //make sure the ondraw is called using this gl context (in case there is more than one) + var old_gl = global.gl; + global.gl = context; + //call ondraw + context.ondraw(); + LEvent.trigger(context,"draw"); + //restore old context + global.gl = old_gl; + } + time = now; + } + this._requestFrame_id = post(loop); //launch main loop + } + + //store binded to be able to remove them if destroyed + /* + var _binded_events = []; + function addEvent(object, type, callback) + { + _binded_events.push(object,type,callback); + } + */ + + /** + * Destroy this WebGL context (removes also the Canvas from the DOM) + * @method destroy + */ + gl.destroy = function() { + //unbind global events + if(onkey_handler) + { + document.removeEventListener("keydown", onkey_handler ); + document.removeEventListener("keyup", onkey_handler ); + } + + if(this.canvas.parentNode) + this.canvas.parentNode.removeChild(this.canvas); + this.destroyed = true; + if(global.gl == this) + global.gl = null; + } + + var mouse = gl.mouse = { + buttons: 0, //this should always be up-to-date with mouse state + last_buttons: 0, //button state in the previous frame + left_button: false, + middle_button: false, + right_button: false, + position: new Float32Array(2), + x:0, //in canvas coordinates + y:0, + deltax: 0, + deltay: 0, + clientx:0, //in client coordinates + clienty:0, + isInsideRect: function(x,y,w,h, flip_y ) + { + var mouse_y = this.y; + if(flip_y) + mouse_y = gl.canvas.height - mouse_y; + if( this.x > x && this.x < x + w && + mouse_y > y && mouse_y < y + h) + return true; + return false; + }, + + /** + * returns true if button num is pressed (where num could be GL.LEFT_MOUSE_BUTTON, GL.RIGHT_MOUSE_BUTTON, GL.MIDDLE_MOUSE_BUTTON + * @method captureMouse + * @param {boolean} capture_wheel capture also the mouse wheel + */ + isButtonPressed: function(num) + { + if(num == GL.LEFT_MOUSE_BUTTON) + return this.buttons & GL.LEFT_MOUSE_BUTTON_MASK; + if(num == GL.MIDDLE_MOUSE_BUTTON) + return this.buttons & GL.MIDDLE_MOUSE_BUTTON_MASK; + if(num == GL.RIGHT_MOUSE_BUTTON) + return this.buttons & GL.RIGHT_MOUSE_BUTTON_MASK; + }, + + wasButtonPressed: function(num) + { + var mask = 0; + if(num == GL.LEFT_MOUSE_BUTTON) + mask = GL.LEFT_MOUSE_BUTTON_MASK; + else if(num == GL.MIDDLE_MOUSE_BUTTON) + mask = GL.MIDDLE_MOUSE_BUTTON_MASK; + else if(num == GL.RIGHT_MOUSE_BUTTON) + mask = GL.RIGHT_MOUSE_BUTTON_MASK; + return (this.buttons & mask) && !(this.last_buttons & mask); + } + }; + + /** + * Tells the system to capture mouse events on the canvas. + * This will trigger onmousedown, onmousemove, onmouseup, onmousewheel callbacks assigned in the gl context + * example: gl.onmousedown = function(e){ ... } + * The event is a regular MouseEvent with some extra parameters + * @method captureMouse + * @param {boolean} capture_wheel capture also the mouse wheel + */ + gl.captureMouse = function(capture_wheel, translate_touchs ) { + + canvas.addEventListener("mousedown", onmouse); + canvas.addEventListener("mousemove", onmouse); + canvas.addEventListener("dragstart", onmouse); + //canvas.addEventListener("mouseup", onmouse); ?? + if(capture_wheel) + { + canvas.addEventListener("mousewheel", onmouse, false); + canvas.addEventListener("wheel", onmouse, false); + //canvas.addEventListener("DOMMouseScroll", onmouse, false); //deprecated or non-standard + } + //prevent right click context menu + canvas.addEventListener("contextmenu", function(e) { e.preventDefault(); return false; }); + + if( translate_touchs ) + this.captureTouch( true ); + } + + function onmouse(e) { + + if(gl.ignore_events) + return; + //console.log(e.type); //debug + var old_mouse_mask = gl.mouse.buttons; + GL.augmentEvent(e, canvas); + e.eventType = e.eventType || e.type; //type cannot be overwritten, so I make a clone to allow me to overwrite + var now = getTime(); + + //gl.mouse info + mouse.dragging = e.dragging; + mouse.position[0] = e.canvasx; + mouse.position[1] = e.canvasy; + mouse.x = e.canvasx; + mouse.y = e.canvasy; + mouse.mousex = e.mousex; + mouse.mousey = e.mousey; + mouse.canvasx = e.canvasx; + mouse.canvasy = e.canvasy; + mouse.clientx = e.mousex; + mouse.clienty = e.mousey; + mouse.buttons = e.buttons; + mouse.left_button = !!(mouse.buttons & GL.LEFT_MOUSE_BUTTON_MASK); + mouse.middle_button = !!(mouse.buttons & GL.MIDDLE_MOUSE_BUTTON_MASK); + mouse.right_button = !!(mouse.buttons & GL.RIGHT_MOUSE_BUTTON_MASK); + + if(e.eventType == "mousedown") + { + if(old_mouse_mask == 0) //no mouse button was pressed till now + { + canvas.removeEventListener("mousemove", onmouse); + var doc = canvas.ownerDocument; + doc.addEventListener("mousemove", onmouse); + doc.addEventListener("mouseup", onmouse); + } + last_click_time = now; + + if(gl.onmousedown) + gl.onmousedown(e); + LEvent.trigger(gl,"mousedown"); + } + else if(e.eventType == "mousemove") + { + if(gl.onmousemove) + gl.onmousemove(e); + LEvent.trigger(gl,"mousemove",e); + } + else if(e.eventType == "mouseup") + { + //console.log("mouseup"); + if(gl.mouse.buttons == 0) //no more buttons pressed + { + canvas.addEventListener("mousemove", onmouse); + var doc = canvas.ownerDocument; + doc.removeEventListener("mousemove", onmouse); + doc.removeEventListener("mouseup", onmouse); + } + e.click_time = now - last_click_time; + //last_click_time = now; //commented to avoid reseting click time when unclicking two mouse buttons + + if(gl.onmouseup) + gl.onmouseup(e); + LEvent.trigger(gl,"mouseup",e); + } + else if((e.eventType == "mousewheel" || e.eventType == "wheel" || e.eventType == "DOMMouseScroll")) + { + e.eventType = "mousewheel"; + if(e.type == "wheel") + e.wheel = -e.deltaY; //in firefox deltaY is 1 while in Chrome is 120 + else + e.wheel = (e.wheelDeltaY != null ? e.wheelDeltaY : e.detail * -60); + + //from stack overflow + //firefox doesnt have wheelDelta + e.delta = e.wheelDelta !== undefined ? (e.wheelDelta/40) : (e.deltaY ? -e.deltaY/3 : 0); + //console.log(e.delta); + if(gl.onmousewheel) + gl.onmousewheel(e); + + LEvent.trigger(gl, "mousewheel", e); + } + else if(e.eventType == "dragstart") + { + if(gl.ondragstart) + gl.ondragstart(e); + LEvent.trigger(gl, "dragstart", e); + } + + if(gl.onmouse) + gl.onmouse(e); + + if(!e.skip_preventDefault) + { + if(e.eventType != "mousemove") + e.stopPropagation(); + e.preventDefault(); + return false; + } + } + + var translate_touches = false; + + gl.captureTouch = function( translate_to_mouse_events ) + { + translate_touches = translate_to_mouse_events; + + canvas.addEventListener("touchstart", ontouch, true); + canvas.addEventListener("touchmove", ontouch, true); + canvas.addEventListener("touchend", ontouch, true); + canvas.addEventListener("touchcancel", ontouch, true); + + canvas.addEventListener('gesturestart', ongesture ); + canvas.addEventListener('gesturechange', ongesture ); + canvas.addEventListener('gestureend', ongesture ); + } + + //translates touch events in mouseevents + function ontouch( e ) + { + var touches = e.changedTouches, + first = touches[0], + type = ""; + + if( gl.ontouch && gl.ontouch(e) === true ) + return; + + if( LEvent.trigger( gl, e.type, e ) === true ) + return; + + if(!translate_touches) + return; + + //ignore secondary touches + if(e.touches.length && e.changedTouches[0].identifier !== e.touches[0].identifier) + return; + + if(touches > 1) + return; + + switch(e.type) + { + case "touchstart": type = "mousedown"; break; + case "touchmove": type = "mousemove"; break; + case "touchend": type = "mouseup"; break; + default: return; + } + + var simulatedEvent = document.createEvent("MouseEvent"); + simulatedEvent.initMouseEvent(type, true, true, window, 1, + first.screenX, first.screenY, + first.clientX, first.clientY, false, + false, false, false, 0/*left*/, null); + simulatedEvent.originalEvent = simulatedEvent; + simulatedEvent.is_touch = true; + first.target.dispatchEvent( simulatedEvent ); + e.preventDefault(); + } + + function ongesture(e) + { + e.eventType = e.type; + + if(gl.ongesture && gl.ongesture(e) === false ) + return; + + if( LEvent.trigger( gl, e.type, e ) === false ) + return; + + e.preventDefault(); + } + + var keys = gl.keys = {}; + + /** + * Tells the system to capture key events on the canvas. This will trigger onkey + * @method captureKeys + * @param {boolean} prevent_default prevent default behaviour (like scroll on the web, etc) + * @param {boolean} only_canvas only caches keyboard events if they happen when the canvas is in focus + */ + var onkey_handler = null; + gl.captureKeys = function( prevent_default, only_canvas ) { + if(onkey_handler) + return; + gl.keys = {}; + + var target = only_canvas ? gl.canvas : document; + + document.addEventListener("keydown", inner ); + document.addEventListener("keyup", inner ); + function inner(e) { onkey(e, prevent_default); } + onkey_handler = inner; + } + + + + function onkey(e, prevent_default) + { + e.eventType = e.type; //type cannot be overwritten, so I make a clone to allow me to overwrite + + var target_element = e.target.nodeName.toLowerCase(); + if(target_element === "input" || target_element === "textarea" || target_element === "select") + return; + + e.character = String.fromCharCode(e.keyCode).toLowerCase(); + var prev_state = false; + var key = GL.mapKeyCode(e.keyCode); + if(!key) //this key doesnt look like an special key + key = e.character; + + //regular key + if (!e.altKey && !e.ctrlKey && !e.metaKey) { + if (key) + gl.keys[key] = e.type == "keydown"; + prev_state = gl.keys[e.keyCode]; + gl.keys[e.keyCode] = e.type == "keydown"; + } + + //avoid repetition if key stays pressed + if(prev_state != gl.keys[e.keyCode]) + { + if(e.type == "keydown" && gl.onkeydown) + gl.onkeydown(e); + else if(e.type == "keyup" && gl.onkeyup) + gl.onkeyup(e); + LEvent.trigger(gl, e.type, e); + } + + if(gl.onkey) + gl.onkey(e); + + if(prevent_default && (e.isChar || GL.blockable_keys[e.keyIdentifier || e.key ]) ) + e.preventDefault(); + } + + //gamepads + gl.gamepads = null; + /* + function onButton(e, pressed) + { + console.log(e); + if(pressed && gl.onbuttondown) + gl.onbuttondown(e); + else if(!pressed && gl.onbuttonup) + gl.onbuttonup(e); + if(gl.onbutton) + gl.onbutton(e); + LEvent.trigger(gl, pressed ? "buttondown" : "buttonup", e ); + } + function onGamepad(e) + { + console.log(e); + if(gl.ongamepad) + gl.ongamepad(e); + } + */ + + /** + * Tells the system to capture gamepad events on the canvas. + * @method captureGamepads + */ + gl.captureGamepads = function() + { + var getGamepads = navigator.getGamepads || navigator.webkitGetGamepads || navigator.mozGetGamepads; + if(!getGamepads) return; + this.gamepads = getGamepads.call(navigator); + + //only in firefox, so I cannot rely on this + /* + window.addEventListener("gamepadButtonDown", function(e) { onButton(e, true); }, false); + window.addEventListener("MozGamepadButtonDown", function(e) { onButton(e, true); }, false); + window.addEventListener("WebkitGamepadButtonDown", function(e) { onButton(e, true); }, false); + window.addEventListener("gamepadButtonUp", function(e) { onButton(e, false); }, false); + window.addEventListener("MozGamepadButtonUp", function(e) { onButton(e, false); }, false); + window.addEventListener("WebkitGamepadButtonUp", function(e) { onButton(e, false); }, false); + window.addEventListener("gamepadconnected", onGamepad, false); + window.addEventListener("gamepaddisconnected", onGamepad, false); + */ + + } + + /** + * returns the detected gamepads on the system + * @method getGamepads + * @param {bool} skip_mapping if set to true it returns the basic gamepad, otherwise it returns a class with mapping info to XBOX controller + */ + gl.getGamepads = function(skip_mapping) + { + //gamepads + var getGamepads = navigator.getGamepads || navigator.webkitGetGamepads || navigator.mozGetGamepads; + if(!getGamepads) + return; + var gamepads = getGamepads.call(navigator); + if(!this.gamepads) + this.gamepads = []; + + //iterate to generate events + for(var i = 0; i < 4; i++) + { + var gamepad = gamepads[i]; //current state + + if(gamepad && !skip_mapping) + addGamepadXBOXmapping(gamepad); + + //launch connected gamepads events + if(gamepad && !gamepad.prev_buttons) + { + gamepad.prev_buttons = new Uint8Array(32); + var event = new CustomEvent("gamepadconnected"); + event.eventType = event.type; + event.gamepad = gamepad; + if(this.ongamepadconnected) + this.ongamepadconnected(event); + LEvent.trigger(gl,"gamepadconnected",event); + } + /* + else if(old_gamepad && !gamepad) + { + var event = new CustomEvent("gamepaddisconnected"); + event.eventType = event.type; + event.gamepad = old_gamepad; + if(this.ongamepaddisconnected) + this.ongamepaddisconnected(event); + LEvent.trigger(gl,"gamepaddisconnected",event); + } + */ + + //seek buttons changes to trigger events + if(gamepad) + { + for(var j = 0; j < gamepad.buttons.length; ++j) + { + var button = gamepad.buttons[j]; + button.was_pressed = false; + if( button.pressed && !gamepad.prev_buttons[j] ) + { + button.was_pressed = true; + var event = new CustomEvent("gamepadButtonDown"); + event.eventType = event.type; + event.button = button; + event.which = j; + event.gamepad = gamepad; + if(gl.onbuttondown) + gl.onbuttondown(event); + LEvent.trigger(gl,"buttondown",event); + } + else if( !button.pressed && gamepad.prev_buttons[j] ) + { + var event = new CustomEvent("gamepadButtonUp"); + event.eventType = event.type; + event.button = button; + event.which = j; + event.gamepad = gamepad; + if(gl.onbuttondown) + gl.onbuttondown(event); + LEvent.trigger(gl,"buttonup",event); + } + + gamepad.prev_buttons[j] = button.pressed ? 1 : 0; + } + } + } + this.gamepads = gamepads; + return gamepads; + } + + function addGamepadXBOXmapping(gamepad) + { + //xbox controller mapping + var xbox = gamepad.xbox || { 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["triggers"] = gamepad.axes[4]; + + for(var i = 0; i < gamepad.buttons.length; i++) + { + switch(i) //I use a switch to ensure that a player with another gamepad could play + { + case 0: xbox.buttons["a"] = gamepad.buttons[i].pressed; break; + case 1: xbox.buttons["b"] = gamepad.buttons[i].pressed; break; + case 2: xbox.buttons["x"] = gamepad.buttons[i].pressed; break; + case 3: xbox.buttons["y"] = gamepad.buttons[i].pressed; break; + case 4: xbox.buttons["lb"] = gamepad.buttons[i].pressed; break; + case 5: xbox.buttons["rb"] = gamepad.buttons[i].pressed; break; + case 6: xbox.buttons["lt"] = gamepad.buttons[i].pressed; break; + case 7: xbox.buttons["rt"] = gamepad.buttons[i].pressed; break; + case 8: xbox.buttons["back"] = gamepad.buttons[i].pressed; break; + case 9: xbox.buttons["start"] = gamepad.buttons[i].pressed; break; + case 10: xbox.buttons["ls"] = gamepad.buttons[i].pressed; break; + case 11: xbox.buttons["rs"] = gamepad.buttons[i].pressed; break; + case 12: if( gamepad.buttons[i].pressed) xbox.hat += "up"; break; + case 13: if( gamepad.buttons[i].pressed) xbox.hat += "down"; break; + case 14: if( gamepad.buttons[i].pressed) xbox.hat += "left"; break; + case 15: if( gamepad.buttons[i].pressed) xbox.hat += "right"; break; + case 16: xbox.buttons["home"] = gamepad.buttons[i].pressed; break; + default: + } + } + gamepad.xbox = xbox; + } + + /** + * launches de canvas in fullscreen mode + * @method fullscreen + */ + gl.fullscreen = function() + { + var canvas = this.canvas; + if(canvas.requestFullScreen) + canvas.requestFullScreen(); + else if(canvas.webkitRequestFullScreen) + canvas.webkitRequestFullScreen(); + else if(canvas.mozRequestFullScreen) + canvas.mozRequestFullScreen(); + else + console.error("Fullscreen not supported"); + } + + /** + * returns a canvas with a snapshot of an area + * this is safer than using the canvas itself due to internals of webgl + * @method snapshot + * @param {Number} startx viewport x coordinate + * @param {Number} starty viewport y coordinate from bottom + * @param {Number} areax viewport area width + * @param {Number} areay viewport area height + * @return {Canvas} canvas + */ + gl.snapshot = function(startx, starty, areax, areay, skip_reverse) + { + var canvas = createCanvas(areax,areay); + var ctx = canvas.getContext("2d"); + var pixels = ctx.getImageData(0,0,canvas.width,canvas.height); + + var buffer = new Uint8Array(areax * areay * 4); + gl.readPixels(startx, starty, canvas.width, canvas.height, gl.RGBA,gl.UNSIGNED_BYTE, buffer); + + pixels.data.set( buffer ); + ctx.putImageData(pixels,0,0); + + if(skip_reverse) + return canvas; + + //flip image + var final_canvas = createCanvas(areax,areay); + var ctx = final_canvas.getContext("2d"); + ctx.translate(0,areay); + ctx.scale(1,-1); + ctx.drawImage(canvas,0,0); + + return final_canvas; + } + + //from https://webgl2fundamentals.org/webgl/lessons/webgl1-to-webgl2.html + function getAndApplyExtension( gl, name ) { + var ext = gl.getExtension(name); + if (!ext) { + return false; + } + var suffix = name.split("_")[0]; + var prefix = suffix = '_'; + var suffixRE = new RegExp(suffix + '$'); + var prefixRE = new RegExp('^' + prefix); + for (var key in ext) { + var val = ext[key]; + if (typeof(val) === 'function') { + // remove suffix (eg: bindVertexArrayOES -> bindVertexArray) + var unsuffixedKey = key.replace(suffixRE, ''); + if (key.substing) + gl[unprefixedKey] = ext[key].bind(ext); + } else { + var unprefixedKey = key.replace(prefixRE, ''); + gl[unprefixedKey] = ext[key]; + } + } + } + + + //mini textures manager + var loading_textures = {}; + /** + * returns a texture and caches it inside gl.textures[] + * @method loadTexture + * @param {String} url + * @param {Object} options (same options as when creating a texture) + * @param {Function} callback function called once the texture is loaded + * @return {Texture} texture + */ + gl.loadTexture = function(url, options, on_load) + { + if(this.textures[ url ]) + return this.textures[url]; + + if( loading_textures[url] ) + return null; + + var img = new Image(); + img.url = url; + img.onload = function() + { + var texture = GL.Texture.fromImage(this, options); + texture.img = this; + gl.textures[this.url] = texture; + delete loading_textures[this.url]; + if(on_load) + on_load(texture); + } + img.src = url; + loading_textures[url] = true; + return null; + } + + /** + * draws a texture to the viewport + * @method drawTexture + * @param {Texture} texture + * @param {number} x in viewport coordinates + * @param {number} y in viewport coordinates + * @param {number} w in viewport coordinates + * @param {number} h in viewport coordinates + * @param {number} tx texture x in texture coordinates + * @param {number} ty texture y in texture coordinates + * @param {number} tw texture width in texture coordinates + * @param {number} th texture height in texture coordinates + */ + gl.drawTexture = (function() { + //static variables: less garbage + var identity = mat3.create(); + var pos = vec2.create(); + var size = vec2.create(); + var area = vec4.create(); + var white = vec4.fromValues(1,1,1,1); + var viewport = vec2.create(); + var _uniforms = {u_texture: 0, u_position: pos, u_color: white, u_size: size, u_texture_area: area, u_viewport: viewport, u_transform: identity }; + + return (function(texture, x,y, w,h, tx,ty, tw,th, shader, uniforms) + { + pos[0] = x; pos[1] = y; + if(w === undefined) + w = texture.width; + if(h === undefined) + h = texture.height; + size[0] = w; + size[1] = h; + + if(tx === undefined) tx = 0; + if(ty === undefined) ty = 0; + if(tw === undefined) tw = texture.width; + if(th === undefined) th = texture.height; + + area[0] = tx / texture.width; + area[1] = ty / texture.height; + area[2] = (tx + tw) / texture.width; + area[3] = (ty + th) / texture.height; + + viewport[0] = this.viewport_data[2]; + viewport[1] = this.viewport_data[3]; + + shader = shader || Shader.getPartialQuadShader(this); + var mesh = Mesh.getScreenQuad(this); + texture.bind(0); + shader.uniforms( _uniforms ); + if( uniforms ) + shader.uniforms( uniforms ); + shader.draw( mesh, gl.TRIANGLES ); + }); + })(); + + gl.canvas.addEventListener("webglcontextlost", function(e) { + e.preventDefault(); + gl.context_lost = true; + if(gl.onlosecontext) + gl.onlosecontext(e); + }, false); + + /** + * use it to reset the the initial gl state + * @method gl.reset + */ + gl.reset = function() + { + //viewport + gl.viewport(0,0, this.canvas.width, this.canvas.height ); + + //flags + gl.disable( gl.BLEND ); + gl.disable( gl.CULL_FACE ); + gl.disable( gl.DEPTH_TEST ); + gl.frontFace( gl.CCW ); + + //texture + gl._current_texture_drawto = null; + gl._current_fbo_color = null; + gl._current_fbo_depth = null; + } + + gl.dump = function() + { + console.log("userAgent: ", navigator.userAgent ); + console.log("Supported extensions:"); + var extensions = gl.getSupportedExtensions(); + console.log( extensions.join(",") ); + var info = [ "VENDOR", "VERSION", "MAX_VERTEX_ATTRIBS", "MAX_VARYING_VECTORS", "MAX_VERTEX_UNIFORM_VECTORS", "MAX_VERTEX_TEXTURE_IMAGE_UNITS", "MAX_FRAGMENT_UNIFORM_VECTORS", "MAX_TEXTURE_SIZE", "MAX_TEXTURE_IMAGE_UNITS" ]; + console.log("WebGL info:"); + for(var i in info) + console.log(" * " + info[i] + ": " + gl.getParameter( gl[info[i]] )); + console.log("*************************************************") + } + + //Reset state + gl.reset(); + + //Return + return gl; +} + +GL.mapKeyCode = function(code) +{ + var named = { + 8: 'BACKSPACE', + 9: 'TAB', + 13: 'ENTER', + 16: 'SHIFT', + 17: 'CTRL', + 27: 'ESCAPE', + 32: 'SPACE', + 37: 'LEFT', + 38: 'UP', + 39: 'RIGHT', + 40: 'DOWN' + }; + return named[code] || (code >= 65 && code <= 90 ? String.fromCharCode(code) : null); +} + +//add useful info to the event +GL.dragging = false; +GL.last_pos = [0,0]; + +//adds extra info to the MouseEvent (coordinates in canvas axis, deltas and button state) +GL.augmentEvent = function(e, root_element) +{ + var offset_left = 0; + var offset_top = 0; + var b = null; + + root_element = root_element || e.target || gl.canvas; + b = root_element.getBoundingClientRect(); + + e.mousex = e.clientX - b.left; + e.mousey = e.clientY - b.top; + e.canvasx = e.mousex; + e.canvasy = b.height - e.mousey; + e.deltax = 0; + e.deltay = 0; + + if(e.type == "mousedown") + { + this.dragging = true; + //gl.mouse.buttons |= (1 << e.which); //enable + } + else if (e.type == "mousemove") + { + } + else if (e.type == "mouseup") + { + //gl.mouse.buttons = gl.mouse.buttons & ~(1 << e.which); + if(e.buttons == 0) + this.dragging = false; + } + + if( e.movementX !== undefined && !GL.isMobile() ) //pointer lock (mobile gives always zero) + { + //console.log( e.movementX ) + e.deltax = e.movementX; + e.deltay = e.movementY; + } + else + { + e.deltax = e.mousex - this.last_pos[0]; + e.deltay = e.mousey - this.last_pos[1]; + } + this.last_pos[0] = e.mousex; + this.last_pos[1] = e.mousey; + + //insert info in event + e.dragging = this.dragging; + e.leftButton = !!(gl.mouse.buttons & GL.LEFT_MOUSE_BUTTON_MASK); + e.middleButton = !!(gl.mouse.buttons & GL.MIDDLE_MOUSE_BUTTON_MASK); + e.rightButton = !!(gl.mouse.buttons & GL.RIGHT_MOUSE_BUTTON_MASK); + //shitty non-coherent API, e.buttons use 1:left,2:right,4:middle) but e.button uses (0:left,1:middle,2:right) + e.buttons_mask = 0; + if( e.leftButton ) e.buttons_mask = 1; + if( e.middleButton ) e.buttons_mask |= 2; + if( e.rightButton ) e.buttons_mask |= 4; + e.isButtonPressed = function(num) { return this.buttons_mask & (1<= 0; --j) //iterate backwards to avoid problems after removing + { + if( array[j][1] != target_instance || (callback && callback !== array[j][0]) ) + continue; + + array.splice(j,1);//remove + } + } + }, + + /** + * Unbinds all callbacks associated to one specific event from this instance + * @method LEvent.unbindAll + * @param {Object} instance where the events are binded + * @param {String} event name of the event you want to remove all binds + **/ + unbindAllEvent: function( instance, event_type ) + { + if(!instance) + throw("cannot unbind events in null"); + + var events = instance.__levents; + if(!events) + return; + delete events[ event_type ]; + if( instance.onLEventUnbindAll ) + instance.onLEventUnbindAll( event_type, target_instance, callback ); + return; + }, + + /** + * Tells if there is a binded callback that matches the criteria + * @method LEvent.isBind + * @param {Object} instance where the are the events binded + * @param {String} event_name string defining the event name + * @param {function} callback the callback + * @param {Object} target_instance [Optional] instance binded to callback + **/ + isBind: function( instance, event_type, callback, target_instance ) + { + if(!instance) + throw("LEvent cannot have null as instance"); + + var events = instance.__levents; + if( !events ) + return; + + if( !events.hasOwnProperty(event_type) ) + return false; + + for(var i = 0, l = events[event_type].length; i < l; ++i) + { + var v = events[event_type][i]; + if(v[0] === callback && v[1] === target_instance) + return true; + } + return false; + }, + + /** + * Tells if there is any callback binded to this event + * @method LEvent.hasBind + * @param {Object} instance where the are the events binded + * @param {String} event_name string defining the event name + * @return {boolean} true is there is at least one + **/ + hasBind: function( instance, event_type ) + { + if(!instance) + throw("LEvent cannot have null as instance"); + var events = instance.__levents; + if(!events || !events.hasOwnProperty( event_type ) || !events[event_type].length) + return false; + return true; + }, + + /** + * Tells if there is any callback binded to this object pointing to a method in the target object + * @method LEvent.hasBindTo + * @param {Object} instance where there are the events binded + * @param {Object} target instance to check to + * @return {boolean} true is there is at least one + **/ + hasBindTo: function( instance, target ) + { + if(!instance) + throw("LEvent cannot have null as instance"); + var events = instance.__levents; + + //no events binded + if(!events) + return false; + + for(var j in events) + { + var binds = events[j]; + for(var i = 0; i < binds.length; ++i) + { + if(binds[i][1] === target) //one found + return true; + } + } + + return false; + }, + + /** + * Triggers and event in an instance + * If the callback returns true then it will stop the propagation and return true + * @method LEvent.trigger + * @param {Object} instance that triggers the event + * @param {String} event_name string defining the event name + * @param {*} parameters that will be received by the binded function + * @param {bool} reverse_order trigger in reverse order (binded last get called first) + * @param {bool} expand_parameters parameters are passed not as one single parameter, but as many + * return {bool} true if the event passed was blocked by any binded callback + **/ + trigger: function( instance, event_type, params, reverse_order, expand_parameters ) + { + if(!instance) + throw("cannot trigger event from null"); + if(instance.constructor === String ) + throw("cannot bind event to a string"); + + var events = instance.__levents; + if( !events || !events.hasOwnProperty(event_type) ) + return false; + + var inst = events[event_type]; + if( reverse_order ) + { + for(var i = inst.length - 1; i >= 0; --i) + { + var v = inst[i]; + if(expand_parameters) + { + if( v && v[0].apply( v[1], params ) === true)// || event.stop) + return true; //stopPropagation + } + else + { + if( v && v[0].call( v[1], event_type, params) === true)// || event.stop) + return true; //stopPropagation + } + } + } + else + { + for(var i = 0, l = inst.length; i < l; ++i) + { + var v = inst[i]; + if( expand_parameters ) + { + if( v && v[0].apply( v[1], params ) === true)// || event.stop) + return true; //stopPropagation + } + else + { + if( v && v[0].call(v[1], event_type, params) === true)// || event.stop) + return true; //stopPropagation + } + } + } + + return false; + }, + + /** + * Triggers and event to every element in an array. + * If the event returns true, it must be intercepted + * @method LEvent.triggerArray + * @param {Array} array contains all instances to triggers the event + * @param {String} event_name string defining the event name + * @param {*} parameters that will be received by the binded function + * @param {bool} reverse_order trigger in reverse order (binded last get called first) + * @param {bool} expand_parameters parameters are passed not as one single parameter, but as many + * return {bool} false + **/ + triggerArray: function( instances, event_type, params, reverse_order, expand_parameters ) + { + var blocked = false; + for(var i = 0, l = instances.length; i < l; ++i) + { + var instance = instances[i]; + if(!instance) + throw("cannot trigger event from null"); + if(instance.constructor === String ) + throw("cannot bind event to a string"); + + var events = instance.__levents; + if( !events || !events.hasOwnProperty( event_type ) ) + continue; + + if( reverse_order ) + { + for(var j = events[event_type].length - 1; j >= 0; --j) + { + var v = events[event_type][j]; + if(expand_parameters) + { + if( v[0].apply(v[1], params ) === true)// || event.stop) + { + blocked = true; + break; //stopPropagation + } + } + else + { + if( v[0].call(v[1], event_type, params) === true)// || event.stop) + { + blocked = true; + break; //stopPropagation + } + } + } + } + else + { + for(var j = 0, ll = events[event_type].length; j < ll; ++j) + { + var v = events[event_type][j]; + if(expand_parameters) + { + if( v[0].apply(v[1], params ) === true)// || event.stop) + { + blocked = true; + break; //stopPropagation + } + } + else + { + if( v[0].call(v[1], event_type, params) === true)// || event.stop) + { + blocked = true; + break; //stopPropagation + } + } + } + } + } + + return blocked; + }, + + extendObject: function( object ) + { + object.bind = function( event_type, callback, instance ){ + return LEvent.bind( this, event_type, callback, instance ); + }; + + object.trigger = function( event_type, params ){ + return LEvent.trigger( this, event_type, params ); + }; + + object.unbind = function( event_type, callback, target_instance ) + { + return LEvent.unbind( this, event_type, callback, instance ); + }; + + object.unbindAll = function( target_instance, callback ) + { + return LEvent.unbindAll( this, target_instance, callback ); + }; + }, + + /** + * Adds the methods to bind, trigger and unbind to this class prototype + * @method LEvent.extendClass + * @param {Object} constructor + **/ + extendClass: function( constructor ) + { + this.extendObject( constructor.prototype ); + } +}; +/* geometric utilities */ +global.CLIP_INSIDE = GL.CLIP_INSIDE = 0; +global.CLIP_OUTSIDE = GL.CLIP_OUTSIDE = 1; +global.CLIP_OVERLAP = GL.CLIP_OVERLAP = 2; + +/** +* @namespace +*/ + + +/** +* Computational geometry algorithms, is a static class +* @class geo +*/ + +global.geo = { + + /** + * Returns a float4 containing the info about a plane with normal N and that passes through point P + * @method createPlane + * @param {vec3} P + * @param {vec3} N + * @return {vec4} plane values + */ + createPlane: function(P,N) + { + return new Float32Array([N[0],N[1],N[2],-vec3.dot(P,N)]); + }, + + /** + * Computes the distance between the point and the plane + * @method distancePointToPlane + * @param {vec3} point + * @param {vec4} plane + * @return {Number} distance + */ + distancePointToPlane: function(point, plane) + { + return (vec3.dot(point,plane) + plane[3])/Math.sqrt(plane[0]*plane[0] + plane[1]*plane[1] + plane[2]*plane[2]); + }, + + /** + * Computes the square distance between the point and the plane + * @method distance2PointToPlane + * @param {vec3} point + * @param {vec4} plane + * @return {Number} distance*distance + */ + distance2PointToPlane: function(point, plane) + { + return (vec3.dot(point,plane) + plane[3])/(plane[0]*plane[0] + plane[1]*plane[1] + plane[2]*plane[2]); + }, + + /** + * Projects a 3D point on a 3D line + * @method projectPointOnLine + * @param {vec3} P + * @param {vec3} A line start + * @param {vec3} B line end + * @param {vec3} result to store result (optional) + * @return {vec3} projectec point + */ + projectPointOnLine: function( P, A, B, result ) + { + result = result || vec3.create(); + //A + dot(AP,AB) / dot(AB,AB) * AB + var AP = vec3.fromValues( P[0] - A[0], P[1] - A[1], P[2] - A[2]); + var AB = vec3.fromValues( B[0] - A[0], B[1] - A[1], B[2] - A[2]); + var div = vec3.dot(AP,AB) / vec3.dot(AB,AB); + result[0] = A[0] + div[0] * AB[0]; + result[1] = A[1] + div[1] * AB[1]; + result[2] = A[2] + div[2] * AB[2]; + return result; + }, + + /** + * Projects a 2D point on a 2D line + * @method project2DPointOnLine + * @param {vec2} P + * @param {vec2} A line start + * @param {vec2} B line end + * @param {vec2} result to store result (optional) + * @return {vec2} projectec point + */ + project2DPointOnLine: function( P, A, B, result ) + { + result = result || vec2.create(); + //A + dot(AP,AB) / dot(AB,AB) * AB + var AP = vec2.fromValues(P[0] - A[0], P[1] - A[1]); + var AB = vec2.fromValues(B[0] - A[0], B[1] - A[1]); + var div = vec2.dot(AP,AB) / vec2.dot(AB,AB); + result[0] = A[0] + div[0] * AB[0]; + result[1] = A[1] + div[1] * AB[1]; + return result; + }, + + /** + * Projects point on plane + * @method projectPointOnPlane + * @param {vec3} point + * @param {vec3} P plane point + * @param {vec3} N plane normal + * @param {vec3} result to store result (optional) + * @return {vec3} projectec point + */ + projectPointOnPlane: function(point, P, N, result) + { + result = result || vec3.create(); + var v = vec3.subtract( vec3.create(), point, P ); + var dist = vec3.dot(v,N); + return vec3.subtract( result, point , vec3.scale( vec3.create(), N, dist ) ); + }, + + /** + * Finds the reflected point over a plane (useful for reflecting camera position when rendering reflections) + * @method reflectPointInPlane + * @param {vec3} point point to reflect + * @param {vec3} P point where the plane passes + * @param {vec3} N normal of the plane + * @return {vec3} reflected point + */ + reflectPointInPlane: function(point, P, N) + { + var d = -1 * (P[0] * N[0] + P[1] * N[1] + P[2] * N[2]); + var t = -(d + N[0]*point[0] + N[1]*point[1] + N[2]*point[2]) / (N[0]*N[0] + N[1]*N[1] + N[2]*N[2]); + //trace("T:" + t); + //var closest = [ point[0]+t*N[0], point[1]+t*N[1], point[2]+t*N[2] ]; + //trace("Closest:" + closest); + return vec3.fromValues( point[0]+t*N[0]*2, point[1]+t*N[1]*2, point[2]+t*N[2]*2 ); + }, + + /** + * test a ray plane collision and retrieves the collision point + * @method testRayPlane + * @param {vec3} start ray start + * @param {vec3} direction ray direction + * @param {vec3} P point where the plane passes + * @param {vec3} N normal of the plane + * @param {vec3} result collision position + * @return {boolean} returns if the ray collides the plane or the ray is parallel to the plane + */ + testRayPlane: function(start, direction, P, N, result) + { + var D = vec3.dot( P, N ); + var numer = D - vec3.dot(N, start); + var denom = vec3.dot(N, direction); + if( Math.abs(denom) < EPSILON) return false; + var t = (numer / denom); + if(t < 0.0) return false; //behind the ray + if(result) + vec3.add( result, start, vec3.scale( result, direction, t) ); + + return true; + }, + + /** + * test collision between segment and plane and retrieves the collision point + * @method testSegmentPlane + * @param {vec3} start segment start + * @param {vec3} end segment end + * @param {vec3} P point where the plane passes + * @param {vec3} N normal of the plane + * @param {vec3} result collision position + * @return {boolean} returns if the segment collides the plane or it is parallel to the plane + */ + testSegmentPlane: (function() { + var temp = vec3.create(); + return function(start, end, P, N, result) + { + var D = vec3.dot( P, N ); + var numer = D - vec3.dot(N, start); + var direction = vec3.sub( temp, end, start ); + var denom = vec3.dot(N, direction); + if( Math.abs(denom) < EPSILON) + return false; //parallel + var t = (numer / denom); + if(t < 0.0) + return false; //behind the start + if(t > 1.0) + return false; //after the end + if(result) + vec3.add( result, start, vec3.scale( result, direction, t) ); + return true; + }; + })(), + + /** + * test a ray sphere collision and retrieves the collision point + * @method testRaySphere + * @param {vec3} start ray start + * @param {vec3} direction ray direction (normalized) + * @param {vec3} center center of the sphere + * @param {number} radius radius of the sphere + * @param {vec3} result [optional] collision position + * @param {number} max_dist not fully tested + * @return {boolean} returns if the ray collides the sphere + */ + testRaySphere: (function() { + var temp = vec3.create(); + return function(start, direction, center, radius, result, max_dist) + { + // sphere equation (centered at origin) x2+y2+z2=r2 + // ray equation x(t) = p0 + t*dir + // substitute x(t) into sphere equation + // solution below: + + // transform ray origin into sphere local coordinates + var orig = vec3.subtract( temp , start, center); + + var a = direction[0]*direction[0] + direction[1]*direction[1] + direction[2]*direction[2]; + var b = 2*orig[0]*direction[0] + 2*orig[1]*direction[1] + 2*orig[2]*direction[2]; + var c = orig[0]*orig[0] + orig[1]*orig[1] + orig[2]*orig[2] - radius*radius; + //return quadraticFormula(a,b,c,t0,t1) ? 2 : 0; + + var q = b*b - 4*a*c; + if( q < 0.0 ) + return false; + + if(result) + { + var sq = Math.sqrt(q); + var d = 1 / (2*a); + var r1 = ( -b + sq ) * d; + var r2 = ( -b - sq ) * d; + var t = r1 < r2 ? r1 : r2; + if(max_dist !== undefined && t > max_dist) + return false; + vec3.add(result, start, vec3.scale( result, direction, t ) ); + } + return true;//real roots + }; + })(), + + /** + * test a ray cylinder collision (only vertical cylinders) and retrieves the collision point [not fully tested] + * @method testRayCylinder + * @param {vec3} start ray start + * @param {vec3} direction ray direction + * @param {vec3} p center of the cylinder + * @param {number} q height of the cylinder + * @param {number} r radius of the cylinder + * @param {vec3} result collision position + * @return {boolean} returns if the ray collides the cylinder + */ + testRayCylinder: function(start, direction, p, q, r, result) + { + var sa = vec3.clone(start); + var sb = vec3.add(vec3.create(), start, vec3.scale( vec3.create(), direction, 100000) ); + var t = 0; + var d = vec3.subtract(vec3.create(),q,p); + var m = vec3.subtract(vec3.create(),sa,p); + var n = vec3.subtract(vec3.create(),sb,sa); + //var n = vec3.create(direction); + + var md = vec3.dot(m, d); + var nd = vec3.dot(n, d); + var dd = vec3.dot(d, d); + + // Test if segment fully outside either endcap of cylinder + if (md < 0.0 && md + nd < 0.0) return false; // Segment outside ’p’ side of cylinder + if (md > dd && md + nd > dd) return false; // Segment outside ’q’ side of cylinder + + var nn = vec3.dot(n, n); + var mn = vec3.dot(m, n); + var a = dd * nn - nd * nd; + var k = vec3.dot(m,m) - r*r; + var c = dd * k - md * md; + + if (Math.abs(a) < EPSILON) + { + // Segment runs parallel to cylinder axis + if (c > 0.0) return false; + // ’a’ and thus the segment lie outside cylinder + // Now known that segment intersects cylinder; figure out how it intersects + if (md < 0.0) t = -mn/nn; + // Intersect segment against ’p’ endcap + else if (md > dd) + t=(nd-mn)/nn; + // Intersect segment against ’q’ endcap + else t = 0.0; + // ’a’ lies inside cylinder + if(result) + vec3.add(result, sa, vec3.scale(result, n,t) ); + return true; + } + var b = dd * mn - nd * md; + var discr = b*b - a*c; + if (discr < 0.0) + return false; + // No real roots; no intersection + t = (-b - Math.sqrt(discr)) / a; + if (t < 0.0 || t > 1.0) + return false; + // Intersection lies outside segment + if(md+t*nd < 0.0) + { + // Intersection outside cylinder on ’p’ side + if (nd <= 0.0) + return false; + // Segment pointing away from endcap + t = -md / nd; + // Keep intersection if Dot(S(t) - p, S(t) - p) <= r^2 + if(result) + vec3.add(result, sa, vec3.scale(result, n,t) ); + return k+2*t*(mn+t*nn) <= 0.0; + } else if (md+t*nd>dd) + { + // Intersection outside cylinder on ’q’ side + if (nd >= 0.0) return false; //Segment pointing away from endcap + t = (dd - md) / nd; + // Keep intersection if Dot(S(t) - q, S(t) - q) <= r^2 + if(result) + vec3.add(result, sa, vec3.scale(result, n,t) ); + return k+dd - 2*md+t*(2*(mn - nd)+t*nn) <= 0.0; + } + // Segment intersects cylinder between the endcaps; t is correct + if(result) + vec3.add(result, sa, vec3.scale(result, n,t) ); + return true; + }, + + + /** + * test a ray bounding-box collision and retrieves the collision point, the BB must be Axis Aligned + * @method testRayBox + * @param {vec3} start ray start + * @param {vec3} direction ray direction + * @param {vec3} minB minimum position of the bounding box + * @param {vec3} maxB maximim position of the bounding box + * @param {vec3} result collision position + * @return {boolean} returns if the ray collides the box + */ + testRayBox: (function() { + + var quadrant = new Float32Array(3); + var candidatePlane = new Float32Array(3); + var maxT = new Float32Array(3); + + return function(start, direction, minB, maxB, result, max_dist) + { + //#define NUMDIM 3 + //#define RIGHT 0 + //#define LEFT 1 + //#define MIDDLE 2 + + max_dist = max_dist || Number.MAX_VALUE; + + var inside = true; + var i = 0|0; + var whichPlane; + + quadrant.fill(0); + maxT.fill(0); + candidatePlane.fill(0); + + /* Find candidate planes; this loop can be avoided if + rays cast all from the eye(assume perpsective view) */ + for (i=0; i < 3; ++i) + if(start[i] < minB[i]) { + quadrant[i] = 1; + candidatePlane[i] = minB[i]; + inside = false; + }else if (start[i] > maxB[i]) { + quadrant[i] = 0; + candidatePlane[i] = maxB[i]; + inside = false; + }else { + quadrant[i] = 2; + } + + /* Ray origin inside bounding box */ + if(inside) { + if(result) + vec3.copy(result, start); + return true; + } + + + /* Calculate T distances to candidate planes */ + for (i = 0; i < 3; ++i) + if (quadrant[i] != 2 && direction[i] != 0.) + maxT[i] = (candidatePlane[i] - start[i]) / direction[i]; + else + maxT[i] = -1.; + + /* Get largest of the maxT's for final choice of intersection */ + whichPlane = 0; + for (i = 1; i < 3; i++) + if (maxT[whichPlane] < maxT[i]) + whichPlane = i; + + /* Check final candidate actually inside box */ + if (maxT[whichPlane] < 0.) return false; + if (maxT[whichPlane] > max_dist) return false; //NOT TESTED + + for (i = 0; i < 3; ++i) + if (whichPlane != i) { + var res = start[i] + maxT[whichPlane] * direction[i]; + if (res < minB[i] || res > maxB[i]) + return false; + if(result) + result[i] = res; + } else { + if(result) + result[i] = candidatePlane[i]; + } + return true; /* ray hits box */ + } + })(), + + /** + * test a ray bounding-box collision, it uses the BBox class and allows to use non-axis aligned bbox + * @method testRayBBox + * @param {vec3} origin ray origin + * @param {vec3} direction ray direction + * @param {BBox} box in BBox format + * @param {mat4} model transformation of the BBox [optional] + * @param {vec3} result collision position in world space unless in_local is true + * @return {boolean} returns if the ray collides the box + */ + testRayBBox: (function(){ + var inv = mat4.create(); + var end = vec3.create(); + var origin2 = vec3.create(); + return function( origin, direction, box, model, result, max_dist, in_local ) + { + if(!origin || !direction || !box) + throw("parameters missing"); + if(model) + { + mat4.invert( inv, model ); + vec3.add( end, origin, direction ); + origin = vec3.transformMat4( origin2, origin, inv); + vec3.transformMat4( end, end, inv ); + vec3.sub( end, end, origin ); + direction = vec3.normalize( end, end ); + } + var r = this.testRayBox( origin, direction, box.subarray(6,9), box.subarray(9,12), result, max_dist ); + if(!in_local && model && result) + vec3.transformMat4(result, result, model); + return r; + } + })(), + + /** + * test if a 3d point is inside a BBox + * @method testPointBBox + * @param {vec3} point + * @param {BBox} bbox + * @return {boolean} true if it is inside + */ + testPointBBox: function(point, bbox) { + if(point[0] < bbox[6] || point[0] > bbox[9] || + point[1] < bbox[7] || point[0] > bbox[10] || + point[2] < bbox[8] || point[0] > bbox[11]) + return false; + return true; + }, + + /** + * test if a BBox overlaps another BBox + * @method testBBoxBBox + * @param {BBox} a + * @param {BBox} b + * @return {boolean} true if it overlaps + */ + testBBoxBBox: function(a, b) + { + var tx = Math.abs( b[0] - a[0]); + if (tx > (a[3] + b[3])) + return false; //outside + var ty = Math.abs(b[1] - a[1]); + if (ty > (a[4] + b[4])) + return false; //outside + var tz = Math.abs( b[2] - a[2]); + if (tz > (a[5] + b[5]) ) + return false; //outside + + var vmin = BBox.getMin(b); + if ( geo.testPointBBox(vmin, a) ) + { + var vmax = BBox.getMax(b); + if (geo.testPointBBox(vmax, a)) + { + return true;// INSIDE;// this instance contains b + } + } + + return true; //OVERLAP; // this instance overlaps with b + }, + + /** + * test if a sphere overlaps a BBox + * @method testSphereBBox + * @param {vec3} point + * @param {float} radius + * @param {BBox} bounding_box + * @return {boolean} true if it overlaps + */ + testSphereBBox: function(center, radius, bbox) + { + // arvo's algorithm from gamasutra + // http://www.gamasutra.com/features/19991018/Gomez_4.htm + + var s, d = 0.0; + //find the square of the distance + //from the sphere to the box + var vmin = BBox.getMin( bbox ); + var vmax = BBox.getMax( bbox ); + for(var i = 0; i < 3; ++i) + { + if( center[i] < vmin[i] ) + { + s = center[i] - vmin[i]; + d += s*s; + } + else if( center[i] > vmax[i] ) + { + s = center[i] - vmax[i]; + d += s*s; + } + } + //return d <= r*r + + var radiusSquared = radius * radius; + if (d <= radiusSquared) + { + return true; + /* + // this is used just to know if it overlaps or is just inside, but I dont care + // make an aabb aabb test with the sphere aabb to test inside state + var halfsize = vec3.fromValues( radius, radius, radius ); + var sphere_bbox = BBox.fromCenterHalfsize( center, halfsize ); + if ( geo.testBBoxBBox(bbox, sphere_bbox) ) + return INSIDE; + return OVERLAP; + */ + } + + return false; //OUTSIDE; + }, + + closestPointBetweenLines: function(a0,a1, b0,b1, p_a, p_b) + { + var u = vec3.subtract( vec3.create(), a1, a0 ); + var v = vec3.subtract( vec3.create(), b1, b0 ); + var w = vec3.subtract( vec3.create(), a0, b0 ); + + var a = vec3.dot(u,u); // always >= 0 + var b = vec3.dot(u,v); + var c = vec3.dot(v,v); // always >= 0 + var d = vec3.dot(u,w); + var e = vec3.dot(v,w); + var D = a*c - b*b; // always >= 0 + var sc, tc; + + // compute the line parameters of the two closest points + if (D < EPSILON) { // the lines are almost parallel + sc = 0.0; + tc = (b>c ? d/b : e/c); // use the largest denominator + } + else { + sc = (b*e - c*d) / D; + tc = (a*e - b*d) / D; + } + + // get the difference of the two closest points + if(p_a) vec3.add(p_a, a0, vec3.scale(vec3.create(),u,sc)); + if(p_b) vec3.add(p_b, b0, vec3.scale(vec3.create(),v,tc)); + + var dP = vec3.add( vec3.create(), w, vec3.subtract( vec3.create(), vec3.scale(vec3.create(),u,sc) , vec3.scale(vec3.create(),v,tc)) ); // = L1(sc) - L2(tc) + return vec3.length(dP); // return the closest distance + }, + + /** + * extract frustum planes given a view-projection matrix + * @method extractPlanes + * @param {mat4} viewprojection matrix + * @return {Float32Array} returns all 6 planes in a float32array[24] + */ + extractPlanes: function(vp, planes) + { + var planes = planes || new Float32Array(4*6); + + //right + planes.set( [vp[3] - vp[0], vp[7] - vp[4], vp[11] - vp[8], vp[15] - vp[12] ], 0); + normalize(0); + + //left + planes.set( [vp[3] + vp[0], vp[ 7] + vp[ 4], vp[11] + vp[ 8], vp[15] + vp[12] ], 4); + normalize(4); + + //bottom + planes.set( [ vp[ 3] + vp[ 1], vp[ 7] + vp[ 5], vp[11] + vp[ 9], vp[15] + vp[13] ], 8); + normalize(8); + + //top + planes.set( [ vp[ 3] - vp[ 1], vp[ 7] - vp[ 5], vp[11] - vp[ 9], vp[15] - vp[13] ],12); + normalize(12); + + //back + planes.set( [ vp[ 3] - vp[ 2], vp[ 7] - vp[ 6], vp[11] - vp[10], vp[15] - vp[14] ],16); + normalize(16); + + //front + planes.set( [ vp[ 3] + vp[ 2], vp[ 7] + vp[ 6], vp[11] + vp[10], vp[15] + vp[14] ],20); + normalize(20); + + return planes; + + function normalize(pos) + { + var N = planes.subarray(pos,pos+3); + var l = vec3.length(N); + if(l === 0) return; + l = 1.0 / l; + planes[pos] *= l; + planes[pos+1] *= l; + planes[pos+2] *= l; + planes[pos+3] *= l; + } + }, + + /** + * test a BBox against the frustum + * @method frustumTestBox + * @param {Float32Array} planes frustum planes + * @param {BBox} boundindbox in BBox format + * @return {enum} CLIP_INSIDE, CLIP_OVERLAP, CLIP_OUTSIDE + */ + frustumTestBox: function(planes, box) + { + var flag = 0, o = 0; + + flag = planeBoxOverlap(planes.subarray(0,4),box); + if (flag == CLIP_OUTSIDE) return CLIP_OUTSIDE; o+= flag; + flag = planeBoxOverlap(planes.subarray(4,8),box); + if (flag == CLIP_OUTSIDE) return CLIP_OUTSIDE; o+= flag; + flag = planeBoxOverlap(planes.subarray(8,12),box); + if (flag == CLIP_OUTSIDE) return CLIP_OUTSIDE; o+= flag; + flag = planeBoxOverlap(planes.subarray(12,16),box); + if (flag == CLIP_OUTSIDE) return CLIP_OUTSIDE; o+= flag; + flag = planeBoxOverlap(planes.subarray(16,20),box); + if (flag == CLIP_OUTSIDE) return CLIP_OUTSIDE; o+= flag; + flag = planeBoxOverlap(planes.subarray(20,24),box); + if (flag == CLIP_OUTSIDE) return CLIP_OUTSIDE; o+= flag; + + return o == 0 ? CLIP_INSIDE : CLIP_OVERLAP; + }, + + /** + * test a Sphere against the frustum + * @method frustumTestSphere + * @param {vec3} center sphere center + * @param {number} radius sphere radius + * @return {enum} CLIP_INSIDE, CLIP_OVERLAP, CLIP_OUTSIDE + */ + + frustumTestSphere: function(planes, center, radius) + { + var dist; + var overlap = false; + + dist = distanceToPlane( planes.subarray(0,4), center ); + if( dist < -radius ) return CLIP_OUTSIDE; + else if(dist >= -radius && dist <= radius) overlap = true; + dist = distanceToPlane( planes.subarray(4,8), center ); + if( dist < -radius ) return CLIP_OUTSIDE; + else if(dist >= -radius && dist <= radius) overlap = true; + dist = distanceToPlane( planes.subarray(8,12), center ); + if( dist < -radius ) return CLIP_OUTSIDE; + else if(dist >= -radius && dist <= radius) overlap = true; + dist = distanceToPlane( planes.subarray(12,16), center ); + if( dist < -radius ) return CLIP_OUTSIDE; + else if(dist >= -radius && dist <= radius) overlap = true; + dist = distanceToPlane( planes.subarray(16,20), center ); + if( dist < -radius ) return CLIP_OUTSIDE; + else if(dist >= -radius && dist <= radius) overlap = true; + dist = distanceToPlane( planes.subarray(20,24), center ); + if( dist < -radius ) return CLIP_OUTSIDE; + else if(dist >= -radius && dist <= radius) overlap = true; + return overlap ? CLIP_OVERLAP : CLIP_INSIDE; + }, + + + /** + * test if a 2d point is inside a 2d polygon + * @method testPoint2DInPolygon + * @param {Array} poly array of 2d points + * @param {vec2} point + * @return {boolean} true if it is inside + */ + testPoint2DInPolygon: function(poly, pt) { + for(var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i) + ((poly[i][1] <= pt[1] && pt[1] < poly[j][1]) || (poly[j][1] <= pt[1] && pt[1] < poly[i][1])) + && (pt[0] < (poly[j][0] - poly[i][0]) * (pt[1] - poly[i][1]) / (poly[j][1] - poly[i][1]) + poly[i][0]) + && (c = !c); + return c; + } +}; + +/** +* BBox is a class to create BoundingBoxes but it works as glMatrix, creating Float32Array with the info inside instead of objects +* The bounding box is stored as center,halfsize,min,max,radius (total of 13 floats) +* @class BBox +*/ +global.BBox = GL.BBox = { + center:0, + halfsize:3, + min:6, + max:9, + radius:12, + data_length: 13, + + //corners: new Float32Array([1,1,1, 1,1,-1, 1,-1,1, 1,-1,-1, -1,1,1, -1,1,-1, -1,-1,1, -1,-1,-1 ]), + corners: [ vec3.fromValues(1,1,1), vec3.fromValues(1,1,-1), vec3.fromValues(1,-1,1), vec3.fromValues(1,-1,-1), vec3.fromValues(-1,1,1), vec3.fromValues(-1,1,-1), vec3.fromValues(-1,-1,1), vec3.fromValues(-1,-1,-1) ] , + + /** + * create an empty bbox + * @method create + * @return {BBox} returns a float32array with the bbox + */ + create: function() + { + return new Float32Array(13); + }, + + /** + * create an bbox copy from another one + * @method clone + * @return {BBox} returns a float32array with the bbox + */ + clone: function(bb) + { + return new Float32Array(bb); + }, + + /** + * copy one bbox into another + * @method copy + * @param {BBox} out where to store the result + * @param {BBox} where to read the bbox + * @return {BBox} returns out + */ + copy: function(out,bb) + { + out.set(bb); + return out; + }, + + /** + * create a bbox from one point + * @method fromPoint + * @param {vec3} point + * @return {BBox} returns a float32array with the bbox + */ + fromPoint: function(point) + { + var bb = this.create(); + bb.set(point, 0); //center + bb.set(point, 6); //min + bb.set(point, 9); //max + return bb; + }, + + /** + * create a bbox from min and max points + * @method fromMinMax + * @param {vec3} min + * @param {vec3} max + * @return {BBox} returns a float32array with the bbox + */ + fromMinMax: function(min,max) + { + var bb = this.create(); + this.setMinMax(bb, min, max); + return bb; + }, + + /** + * create a bbox from center and halfsize + * @method fromCenterHalfsize + * @param {vec3} center + * @param {vec3} halfsize + * @return {BBox} returns a float32array with the bbox + */ + fromCenterHalfsize: function(center, halfsize) + { + var bb = this.create(); + this.setCenterHalfsize(bb, center, halfsize); + return bb; + }, + + /** + * create a bbox from a typed-array containing points + * @method fromPoints + * @param {Float32Array} points + * @return {BBox} returns a float32array with the bbox + */ + fromPoints: function(points) + { + var bb = this.create(); + this.setFromPoints(bb, points); + return bb; + }, + + /** + * set the values to a BB from a set of points + * @method setFromPoints + * @param {BBox} out where to store the result + * @param {Float32Array} points + * @return {BBox} returns a float32array with the bbox + */ + setFromPoints: function(bb, points) + { + var min = bb.subarray(6,9); + var max = bb.subarray(9,12); + + min[0] = points[0]; //min.set( points.subarray(0,3) ); + min[1] = points[1]; + min[2] = points[2]; + max.set( min ); + + var v = 0; + for(var i = 3, l = points.length; i < l; i+=3) + { + var x = points[i]; + var y = points[i+1]; + var z = points[i+2]; + if( x < min[0] ) min[0] = x; + else if( x > max[0] ) max[0] = x; + if( y < min[1] ) min[1] = y; + else if( y > max[1] ) max[1] = y; + if( z < min[2] ) min[2] = z; + else if( z > max[2] ) max[2] = z; + /* + v = points.subarray(i,i+3); + vec3.min( min, v, min); + vec3.max( max, v, max); + */ + } + + //center + bb[0] = (min[0] + max[0]) * 0.5; + bb[1] = (min[1] + max[1]) * 0.5; + bb[2] = (min[2] + max[2]) * 0.5; + //halfsize + bb[3] = max[0] - bb[0]; + bb[4] = max[1] - bb[1]; + bb[5] = max[2] - bb[2]; + bb[12] = Math.sqrt( bb[3]*bb[3] + bb[4]*bb[4] + bb[5]*bb[5] ); + + /* + var center = vec3.add( bb.subarray(0,3), min, max ); + vec3.scale( center, center, 0.5); + vec3.subtract( bb.subarray(3,6), max, center ); + bb[12] = vec3.length(bb.subarray(3,6)); //radius + */ + return bb; + }, + + /** + * set the values to a BB from min and max + * @method setMinMax + * @param {BBox} out where to store the result + * @param {vec3} min + * @param {vec3} max + * @return {BBox} returns out + */ + setMinMax: function(bb, min, max) + { + bb[6] = min[0]; + bb[7] = min[1]; + bb[8] = min[2]; + bb[9] = max[0]; + bb[10] = max[1]; + bb[11] = max[2]; + + //halfsize + var halfsize = bb.subarray(3,6); + vec3.sub( halfsize, max, min ); //range + vec3.scale( halfsize, halfsize, 0.5 ); + + //center + bb[0] = max[0] - halfsize[0]; + bb[1] = max[1] - halfsize[1]; + bb[2] = max[2] - halfsize[2]; + + bb[12] = vec3.length(bb.subarray(3,6)); //radius + return bb; + }, + + /** + * set the values to a BB from center and halfsize + * @method setCenterHalfsize + * @param {BBox} out where to store the result + * @param {vec3} min + * @param {vec3} max + * @param {number} radius [optional] (the minimum distance from the center to the further point) + * @return {BBox} returns out + */ + setCenterHalfsize: function(bb, center, halfsize, radius) + { + bb[0] = center[0]; + bb[1] = center[1]; + bb[2] = center[2]; + bb[3] = halfsize[0]; + bb[4] = halfsize[1]; + bb[5] = halfsize[2]; + bb[6] = bb[0] - bb[3]; + bb[7] = bb[1] - bb[4]; + bb[8] = bb[2] - bb[5]; + bb[9] = bb[0] + bb[3]; + bb[10] = bb[1] + bb[4]; + bb[11] = bb[2] + bb[5]; + if(radius) + bb[12] = radius; + else + bb[12] = vec3.length(halfsize); + return bb; + }, + + /** + * Apply a matrix transformation to the BBox (applies to every corner and recomputes the BB) + * @method transformMat4 + * @param {BBox} out where to store the result + * @param {BBox} bb bbox you want to transform + * @param {mat4} mat transformation + * @return {BBox} returns out + */ + transformMat4: (function(){ + var hsx = 0; + var hsy = 0; + var hsz = 0; + var points_buffer = new Float32Array(8*3); + var points = []; + for(var i = 0; i < 24; i += 3 ) + points.push( points_buffer.subarray( i, i+3 ) ); + + return function( out, bb, mat ) + { + var centerx = bb[0]; + var centery = bb[1]; + var centerz = bb[2]; + hsx = bb[3]; + hsy = bb[4]; + hsz = bb[5]; + + var corners = this.corners; + + for(var i = 0; i < 8; ++i) + { + var corner = corners[i]; + var result = points[i]; + result[0] = hsx * corner[0] + centerx; + result[1] = hsy * corner[1] + centery; + result[2] = hsz * corner[2] + centerz; + mat4.multiplyVec3( result, mat, result ); + } + + return this.setFromPoints( out, points_buffer ); + } + })(), + + + /** + * Computes the eight corners of the BBox and returns it + * @method getCorners + * @param {BBox} bb the bounding box + * @param {Float32Array} result optional, should be 8 * 3 + * @return {Float32Array} returns the 8 corners + */ + getCorners: function( bb, result ) + { + var center = bb; //.subarray(0,3); AVOID GC + var halfsize = bb.subarray(3,6); + + var corners = null; + if(result) + { + result.set(this.corners); + corners = result; + } + else + corners = new Float32Array( this.corners ); + + for(var i = 0; i < 8; ++i) + { + var corner = corners.subarray(i*3, i*3+3); + vec3.multiply( corner, halfsize, corner ); + vec3.add( corner, corner, center ); + } + + return corners; + }, + + merge: function( out, a, b ) + { + var min = out.subarray(6,9); + var max = out.subarray(9,12); + vec3.min( min, a.subarray(6,9), b.subarray(6,9) ); + vec3.max( max, a.subarray(9,12), b.subarray(9,12) ); + return BBox.setMinMax( out, min, max ); + }, + + extendToPoint: function( out, p ) + { + if( p[0] < out[6] ) out[6] = p[0]; + else if( p[0] > out[9] ) out[9] = p[0]; + + if( p[1] < out[7] ) out[7] = p[1]; + else if( p[1] > out[10] ) out[10] = p[1]; + + + if( p[2] < out[8] ) out[8] = p[2]; + else if( p[2] > out[11] ) out[11] = p[2]; + + //recompute + var min = out.subarray(6,9); + var max = out.subarray(9,12); + var center = vec3.add( out.subarray(0,3), min, max ); + vec3.scale( center, center, 0.5); + vec3.subtract( out.subarray(3,6), max, center ); + out[12] = vec3.length( out.subarray(3,6) ); //radius + return out; + }, + + clampPoint: function(out, box, point) + { + out[0] = Math.clamp( point[0], box[0] - box[3], box[0] + box[3]); + out[1] = Math.clamp( point[1], box[1] - box[4], box[1] + box[4]); + out[2] = Math.clamp( point[2], box[2] - box[5], box[2] + box[5]); + }, + + isPointInside: function( bbox, point ) + { + if( (bbox[0] - bbox[3]) > point[0] || + (bbox[1] - bbox[4]) > point[1] || + (bbox[2] - bbox[5]) > point[2] || + (bbox[0] + bbox[3]) < point[0] || + (bbox[1] + bbox[4]) < point[1] || + (bbox[2] + bbox[5]) < point[2] ) + return false; + return true; + }, + + getCenter: function(bb) { return bb.subarray(0,3); }, + getHalfsize: function(bb) { return bb.subarray(3,6); }, + getMin: function(bb) { return bb.subarray(6,9); }, + getMax: function(bb) { return bb.subarray(9,12); }, + getRadius: function(bb) { return bb[12]; } + //setCenter,setHalfsize not coded, too much work to update all +} + +global.distanceToPlane = GL.distanceToPlane = function distanceToPlane(plane, point) +{ + return vec3.dot(plane,point) + plane[3]; +} + +global.planeBoxOverlap = GL.planeBoxOverlap = function planeBoxOverlap(plane, box) +{ + var n = plane; //.subarray(0,3); + var d = plane[3]; + //hack, to avoif GC I use indices directly + var center = box; //.subarray(0,3); + var halfsize = box; //.subarray(3,6); + + var radius = Math.abs( halfsize[3] * n[0] ) + Math.abs( halfsize[4] * n[1] ) + Math.abs( halfsize[5] * n[2] ); + var distance = vec3.dot(n,center) + d; + + if (distance <= -radius) + return CLIP_OUTSIDE; + else if (distance <= radius) + return CLIP_OVERLAP; + return CLIP_INSIDE; +} + +/** +* @namespace GL +*/ + +/** +* Octree generator for fast ray triangle collision with meshes +* Dependencies: glmatrix.js (for vector and matrix operations) +* @class Octree +* @constructor +* @param {Mesh} mesh object containing vertices buffer (indices buffer optional) +*/ + +global.Octree = GL.Octree = function Octree( mesh ) +{ + this.root = null; + this.total_depth = 0; + this.total_nodes = 0; + if(mesh) + { + this.buildFromMesh(mesh); + this.total_nodes = this.trim(); + } +} + +Octree.MAX_NODE_TRIANGLES_RATIO = 0.1; +Octree.MAX_OCTREE_DEPTH = 8; +Octree.OCTREE_MARGIN_RATIO = 0.01; +Octree.OCTREE_MIN_MARGIN = 0.1; + +var octree_tested_boxes = 0; +var octree_tested_triangles = 0; + +Octree.prototype.buildFromMesh = function( mesh ) +{ + this.total_depth = 0; + this.total_nodes = 0; + + var vertices = mesh.getBuffer("vertices").data; + var triangles = mesh.getIndexBuffer("triangles"); + if(triangles) + triangles = triangles.data; //get the internal data + + var root = this.computeAABB(vertices); + this.root = root; + this.total_nodes = 1; + this.total_triangles = triangles ? triangles.length / 3 : vertices.length / 9; + this.max_node_triangles = this.total_triangles * Octree.MAX_NODE_TRIANGLES_RATIO; + + var margin = vec3.create(); + vec3.scale( margin, root.size, Octree.OCTREE_MARGIN_RATIO ); + if(margin[0] < Octree.OCTREE_MIN_MARGIN) margin[0] = Octree.OCTREE_MIN_MARGIN; + if(margin[1] < Octree.OCTREE_MIN_MARGIN) margin[1] = Octree.OCTREE_MIN_MARGIN; + if(margin[2] < Octree.OCTREE_MIN_MARGIN) margin[2] = Octree.OCTREE_MIN_MARGIN; + + vec3.sub(root.min, root.min, margin); + vec3.add(root.max, root.max, margin); + + root.faces = []; + root.inside = 0; + + + //indexed + if(triangles) + { + for(var i = 0; i < triangles.length; i+=3) + { + var face = new Float32Array([vertices[triangles[i]*3], vertices[triangles[i]*3+1],vertices[triangles[i]*3+2], + vertices[triangles[i+1]*3], vertices[triangles[i+1]*3+1],vertices[triangles[i+1]*3+2], + vertices[triangles[i+2]*3], vertices[triangles[i+2]*3+1],vertices[triangles[i+2]*3+2],i/3]); + this.addToNode( face,root,0); + } + } + else + { + for(var i = 0; i < vertices.length; i+=9) + { + var face = new Float32Array( 10 ); + face.set( vertices.subarray(i,i+9) ); + face[9] = i/9; + this.addToNode(face,root,0); + } + } + + return root; +} + +Octree.prototype.addToNode = function( face, node, depth ) +{ + node.inside += 1; + + //has children + if(node.c) + { + var aabb = this.computeAABB(face); + var added = false; + for(var i in node.c) + { + var child = node.c[i]; + if (Octree.isInsideAABB(aabb,child)) + { + this.addToNode(face,child, depth+1); + added = true; + break; + } + } + if(!added) + { + if(node.faces == null) + node.faces = []; + node.faces.push(face); + } + } + else //add till full, then split + { + if(node.faces == null) + node.faces = []; + node.faces.push(face); + + //split + if(node.faces.length > this.max_node_triangles && depth < Octree.MAX_OCTREE_DEPTH) + { + this.splitNode(node); + if(this.total_depth < depth + 1) + this.total_depth = depth + 1; + + var faces = node.faces.concat(); + node.faces = null; + + //redistribute all nodes + for(var i in faces) + { + var face = faces[i]; + var aabb = this.computeAABB(face); + var added = false; + for(var j in node.c) + { + var child = node.c[j]; + if (Octree.isInsideAABB(aabb,child)) + { + this.addToNode(face,child, depth+1); + added = true; + break; + } + } + if (!added) + { + if(node.faces == null) + node.faces = []; + node.faces.push(face); + } + } + } + } +}; + +Octree.prototype.octree_pos_ref = [[0,0,0],[0,0,1],[0,1,0],[0,1,1],[1,0,0],[1,0,1],[1,1,0],[1,1,1]]; + +Octree.prototype.splitNode = function(node) +{ + node.c = []; + var half = [(node.max[0] - node.min[0]) * 0.5, (node.max[1] - node.min[1]) * 0.5, (node.max[2] - node.min[2]) * 0.5]; + + for(var i in this.octree_pos_ref) + { + var ref = this.octree_pos_ref[i]; + + var newnode = {}; + this.total_nodes += 1; + + newnode.min = [ node.min[0] + half[0] * ref[0], node.min[1] + half[1] * ref[1], node.min[2] + half[2] * ref[2]]; + newnode.max = [newnode.min[0] + half[0], newnode.min[1] + half[1], newnode.min[2] + half[2]]; + newnode.faces = null; + newnode.inside = 0; + node.c.push(newnode); + } +} + +Octree.prototype.computeAABB = function(vertices) +{ + var min = new Float32Array([ vertices[0], vertices[1], vertices[2] ]); + var max = new Float32Array([ vertices[0], vertices[1], vertices[2] ]); + + for(var i = 0; i < vertices.length; i+=3) + { + for(var j = 0; j < 3; j++) + { + if(min[j] > vertices[i+j]) + min[j] = vertices[i+j]; + if(max[j] < vertices[i+j]) + max[j] = vertices[i+j]; + } + } + + return {min: min, max: max, size: vec3.sub( vec3.create(), max, min) }; +} + +//remove empty nodes +Octree.prototype.trim = function(node) +{ + node = node || this.root; + if(!node.c) + return 1; + + var num = 1; + var valid = []; + var c = node.c; + for(var i = 0; i < c.length; ++i) + { + if(c[i].inside) + { + valid.push(c[i]); + num += this.trim(c[i]); + } + } + node.c = valid; + return num; +} + +/** +* Test collision between ray and triangles in the octree +* @method testRay +* @param {vec3} origin ray origin position +* @param {vec3} direction ray direction position +* @param {number} dist_min +* @param {number} dist_max +* @return {HitTest} object containing pos and normal +*/ +Octree.prototype.testRay = (function(){ + var origin_temp = vec3.create(); + var direction_temp = vec3.create(); + var min_temp = vec3.create(); + var max_temp = vec3.create(); + + return function(origin, direction, dist_min, dist_max, test_backfaces ) + { + octree_tested_boxes = 0; + octree_tested_triangles = 0; + + if(!this.root) + { + throw("Error: octree not build"); + } + + origin_temp.set( origin ); + direction_temp.set( direction ); + min_temp.set( this.root.min ); + max_temp.set( this.root.max ); + + var test = Octree.hitTestBox( origin_temp, direction_temp, min_temp, max_temp ); + if(!test) //no collision with mesh bounding box + return null; + + var test = Octree.testRayInNode( this.root, origin_temp, direction_temp, test_backfaces ); + if(test != null) + { + var pos = vec3.scale( vec3.create(), direction, test.t ); + vec3.add( pos, pos, origin ); + test.pos = pos; + return test; + } + + return null; + } +})(); + +/** +* test collision between sphere and the triangles in the octree (only test if there is any vertex inside the sphere) +* @method testSphere +* @param {vec3} origin sphere center +* @param {number} radius +* @return {Boolean} true if the sphere collided with the mesh +*/ +Octree.prototype.testSphere = function( origin, radius ) +{ + origin = vec3.clone(origin); + octree_tested_boxes = 0; + octree_tested_triangles = 0; + + if(!this.root) + throw("Error: octree not build"); + + //better to use always the radius squared, because all the calculations are going to do that + var rr = radius * radius; + + if( !Octree.testSphereBox( origin, rr, vec3.clone(this.root.min), vec3.clone(this.root.max) ) ) + return false; //out of the box + + return Octree.testSphereInNode( this.root, origin, rr ); +} + +//WARNING: cannot use static here, it uses recursion +Octree.testRayInNode = function( node, origin, direction, test_backfaces ) +{ + var test = null; + var prev_test = null; + octree_tested_boxes += 1; + + //test faces + if(node.faces) + for(var i = 0, l = node.faces.length; i < l; ++i) + { + var face = node.faces[i]; + octree_tested_triangles += 1; + test = Octree.hitTestTriangle( origin, direction, face.subarray(0,3) , face.subarray(3,6), face.subarray(6,9), test_backfaces ); + if (test==null) + continue; + test.face = face; + if(prev_test) + prev_test.mergeWith( test ); + else + prev_test = test; + } + + //WARNING: cannot use statics here, this function uses recursion + var child_min = vec3.create(); + var child_max = vec3.create(); + + //test children nodes faces + var child; + if(node.c) + for(var i = 0; i < node.c.length; ++i) + { + child = node.c[i]; + child_min.set( child.min ); + child_max.set( child.max ); + + //test with node box + test = Octree.hitTestBox( origin, direction, child_min, child_max ); + if( test == null ) + continue; + + //nodebox behind current collision, then ignore node + if(prev_test && test.t > prev_test.t) + continue; + + //test collision with node + test = Octree.testRayInNode( child, origin, direction, test_backfaces ); + if(test == null) + continue; + + if(prev_test) + prev_test.mergeWith( test ); + else + prev_test = test; + } + + return prev_test; +} + +//WARNING: cannot use static here, it uses recursion +Octree.testSphereInNode = function( node, origin, radius2 ) +{ + var test = null; + var prev_test = null; + octree_tested_boxes += 1; + + //test faces + if(node.faces) + for(var i = 0, l = node.faces.length; i < l; ++i) + { + var face = node.faces[i]; + octree_tested_triangles += 1; + if( Octree.testSphereTriangle( origin, radius2, face.subarray(0,3) , face.subarray(3,6), face.subarray(6,9) ) ) + return true; + } + + //WARNING: cannot use statics here, this function uses recursion + var child_min = vec3.create(); + var child_max = vec3.create(); + + //test children nodes faces + var child; + if(node.c) + for(var i = 0; i < node.c.length; ++i) + { + child = node.c[i]; + child_min.set( child.min ); + child_max.set( child.max ); + + //test with node box + if( !Octree.testSphereBox( origin, radius2, child_min, child_max ) ) + continue; + + //test collision with node content + if( Octree.testSphereInNode( child, origin, radius2 ) ) + return true; + } + + return false; +} + +//test if one bounding is inside or overlapping another bounding +Octree.isInsideAABB = function(a,b) +{ + if(a.min[0] < b.min[0] || a.min[1] < b.min[1] || a.min[2] < b.min[2] || + a.max[0] > b.max[0] || a.max[1] > b.max[1] || a.max[2] > b.max[2]) + return false; + return true; +} + + +Octree.hitTestBox = (function(){ + var tMin = vec3.create(); + var tMax = vec3.create(); + var inv = vec3.create(); + var t1 = vec3.create(); + var t2 = vec3.create(); + var tmp = vec3.create(); + var epsilon = 1.0e-6; + var eps = vec3.fromValues( epsilon,epsilon,epsilon ); + + return function( origin, ray, box_min, box_max ) { + vec3.subtract( tMin, box_min, origin ); + vec3.subtract( tMax, box_max, origin ); + + if( vec3.maxValue(tMin) < 0 && vec3.minValue(tMax) > 0) + return new HitTest(0,origin,ray); + + inv[0] = 1/ray[0]; inv[1] = 1/ray[1]; inv[2] = 1/ray[2]; + vec3.multiply(tMin, tMin, inv); + vec3.multiply(tMax, tMax, inv); + vec3.min(t1, tMin, tMax); + vec3.max(t2, tMin, tMax); + var tNear = vec3.maxValue(t1); + var tFar = vec3.minValue(t2); + + if (tNear > 0 && tNear < tFar) { + var hit = vec3.add( vec3.create(), vec3.scale(tmp, ray, tNear ), origin); + vec3.add( box_min, box_min, eps); + vec3.subtract(box_min, box_min, eps); + return new HitTest(tNear, hit, vec3.fromValues( + (hit[0] > box_max[0]) - (hit[0] < box_min[0]), + (hit[1] > box_max[1]) - (hit[1] < box_min[1]), + (hit[2] > box_max[2]) - (hit[2] < box_min[2]) )); + } + + return null; + } +})(); + +Octree.hitTestTriangle = (function(){ + + var AB = vec3.create(); + var AC = vec3.create(); + var toHit = vec3.create(); + var tmp = vec3.create(); + + return function( origin, ray, A, B, C, test_backfaces ) { + vec3.subtract( AB, B, A ); + vec3.subtract( AC, C, A ); + var normal = vec3.cross( vec3.create(), AB, AC ); //returned + vec3.normalize( normal, normal ); + if( !test_backfaces && vec3.dot(normal,ray) > 0) + return null; //ignore backface + + var t = vec3.dot(normal, vec3.subtract( tmp, A, origin )) / vec3.dot(normal,ray); + + if (t > 0) + { + var hit = vec3.scale(vec3.create(), ray, t); //returned + vec3.add(hit, hit, origin); + vec3.subtract( toHit, hit, A ); + var dot00 = vec3.dot(AC,AC); + var dot01 = vec3.dot(AC,AB); + var dot02 = vec3.dot(AC,toHit); + var dot11 = vec3.dot(AB,AB); + var dot12 = vec3.dot(AB,toHit); + var divide = dot00 * dot11 - dot01 * dot01; + var u = (dot11 * dot02 - dot01 * dot12) / divide; + var v = (dot00 * dot12 - dot01 * dot02) / divide; + if (u >= 0 && v >= 0 && u + v <= 1) + return new HitTest(t, hit, normal); + } + return null; + }; +})(); + +//from http://realtimecollisiondetection.net/blog/?p=103 +//radius must be squared +Octree.testSphereTriangle = (function(){ + + var A = vec3.create(); + var B = vec3.create(); + var C = vec3.create(); + var AB = vec3.create(); + var AC = vec3.create(); + var BC = vec3.create(); + var CA = vec3.create(); + var V = vec3.create(); + + return function( P, rr, A_, B_, C_ ) { + vec3.sub( A, A_, P ); + vec3.sub( B, B_, P ); + vec3.sub( C, C_, P ); + + vec3.sub( AB, B, A ); + vec3.sub( AC, C, A ); + + vec3.cross( V, AB, AC ); + var d = vec3.dot( A, V ); + var e = vec3.dot( V, V ); + var sep1 = d * d > rr * e; + var aa = vec3.dot(A, A); + var ab = vec3.dot(A, B); + var ac = vec3.dot(A, C); + var bb = vec3.dot(B, B); + var bc = vec3.dot(B, C); + var cc = vec3.dot(C, C); + var sep2 = (aa > rr) & (ab > aa) & (ac > aa); + var sep3 = (bb > rr) & (ab > bb) & (bc > bb); + var sep4 = (cc > rr) & (ac > cc) & (bc > cc); + + var d1 = ab - aa; + var d2 = bc - bb; + var d3 = ac - cc; + + vec3.sub( BC, C, B ); + vec3.sub( CA, A, C ); + + var e1 = vec3.dot(AB, AB); + var e2 = vec3.dot(BC, BC); + var e3 = vec3.dot(CA, CA); + + var Q1 = vec3.scale(vec3.create(), A, e1); vec3.sub( Q1, Q1, vec3.scale(vec3.create(), AB, d1) ); + var Q2 = vec3.scale(vec3.create(), B, e2); vec3.sub( Q2, Q2, vec3.scale(vec3.create(), BC, d2) ); + var Q3 = vec3.scale(vec3.create(), C, e3); vec3.sub( Q3, Q3, vec3.scale(vec3.create(), CA, d3) ); + + var QC = vec3.scale( vec3.create(), C, e1 ); QC = vec3.sub( QC, QC, Q1 ); + var QA = vec3.scale( vec3.create(), A, e2 ); QA = vec3.sub( QA, QA, Q2 ); + var QB = vec3.scale( vec3.create(), B, e3 ); QB = vec3.sub( QB, QB, Q3 ); + + var sep5 = ( vec3.dot(Q1, Q1) > rr * e1 * e1) & (vec3.dot(Q1, QC) > 0 ); + var sep6 = ( vec3.dot(Q2, Q2) > rr * e2 * e2) & (vec3.dot(Q2, QA) > 0 ); + var sep7 = ( vec3.dot(Q3, Q3) > rr * e3 * e3) & (vec3.dot(Q3, QB) > 0 ); + + var separated = sep1 | sep2 | sep3 | sep4 | sep5 | sep6 | sep7 + return !separated; + }; +})(); + +Octree.testSphereBox = function( center, radius2, box_min, box_max ) { + + // arvo's algorithm from gamasutra + // http://www.gamasutra.com/features/19991018/Gomez_4.htm + var s, d = 0.0; + //find the square of the distance + //from the sphere to the box + for(var i = 0; i < 3; ++i) + { + if( center[i] < box_min[i] ) + { + s = center[i] - box_min[i]; + d += s*s; + } + else if( center[i] > box_max[i] ) + { + s = center[i] - box_max[i]; + d += s*s; + } + } + //return d <= r*r + + if (d <= radius2) + { + return true; + /* + // this is used just to know if it overlaps or is just inside, but I dont care + // make an aabb aabb test with the sphere aabb to test inside state + var halfsize = vec3.fromValues( radius, radius, radius ); + var sphere_bbox = BBox.fromCenterHalfsize( center, halfsize ); + if ( geo.testBBoxBBox(bbox, sphere_bbox) ) + return INSIDE; + return OVERLAP; + */ + } + + return false; //OUTSIDE; +}; +// Provides a convenient raytracing interface. + +// ### new GL.HitTest([t, hit, normal]) +// +// This is the object used to return hit test results. If there are no +// arguments, the constructed argument represents a hit infinitely far +// away. +global.HitTest = GL.HitTest = function HitTest(t, hit, normal) { + this.t = arguments.length ? t : Number.MAX_VALUE; + this.hit = hit; + this.normal = normal; + this.face = null; +} + +// ### .mergeWith(other) +// +// Changes this object to be the closer of the two hit test results. +HitTest.prototype = { + mergeWith: function(other) { + if (other.t > 0 && other.t < this.t) { + this.t = other.t; + this.hit = other.hit; + this.normal = other.normal; + this.face = other.face; + } + } +}; + +// ### new GL.Ray( origin, direction ) +global.Ray = GL.Ray = function Ray( origin, direction ) +{ + this.origin = vec3.create(); + this.direction = vec3.create(); + this.collision_point = vec3.create(); + + if(origin) + this.origin.set( origin ); + if(direction) + this.direction.set( direction ); +} + +Ray.prototype.testPlane = function( P, N ) +{ + return geo.testRayPlane( this.origin, this.direction, P, N, this.collision_point ); +} + +Ray.prototype.testSphere = function( center, radius, max_dist ) +{ + return geo.testRaySphere( this.origin, this.direction, center, radius, this.collision_point, max_dist ); +} + +// ### new GL.Raytracer() +// +// This will read the current modelview matrix, projection matrix, and viewport, +// reconstruct the eye position, and store enough information to later generate +// per-pixel rays using `getRayForPixel()`. +// +// Example usage: +// +// var tracer = new GL.Raytracer(); +// var ray = tracer.getRayForPixel( +// gl.canvas.width / 2, +// gl.canvas.height / 2); +// var result = GL.Raytracer.hitTestSphere( +// tracer.eye, ray, new GL.Vector(0, 0, 0), 1); + +global.Raytracer = GL.Raytracer = function Raytracer( viewprojection_matrix, viewport ) { + this.viewport = vec4.create(); + this.ray00 = vec3.create(); + this.ray10 = vec3.create(); + this.ray01 = vec3.create(); + this.ray11 = vec3.create(); + this.eye = vec3.create(); + this.setup( viewprojection_matrix, viewport ); +} + +Raytracer.prototype.setup = function( viewprojection_matrix, viewport ) +{ + viewport = viewport || gl.viewport_data; + this.viewport.set( viewport ); + + var minX = viewport[0], maxX = minX + viewport[2]; + var minY = viewport[1], maxY = minY + viewport[3]; + + vec3.set( this.ray00, minX, minY, 1 ); + vec3.set( this.ray10, maxX, minY, 1 ); + vec3.set( this.ray01, minX, maxY, 1 ); + vec3.set( this.ray11, maxX, maxY, 1 ); + vec3.unproject( this.ray00, this.ray00, viewprojection_matrix, viewport); + vec3.unproject( this.ray10, this.ray10, viewprojection_matrix, viewport); + vec3.unproject( this.ray01, this.ray01, viewprojection_matrix, viewport); + vec3.unproject( this.ray11, this.ray11, viewprojection_matrix, viewport); + var eye = this.eye; + vec3.unproject(eye, eye, viewprojection_matrix, viewport); + vec3.subtract(this.ray00, this.ray00, eye); + vec3.subtract(this.ray10, this.ray10, eye); + vec3.subtract(this.ray01, this.ray01, eye); + vec3.subtract(this.ray11, this.ray11, eye); +} + + // ### .getRayForPixel(x, y) + // + // Returns the ray direction originating from the camera and traveling through the pixel `x, y`. +Raytracer.prototype.getRayForPixel = (function(){ + var ray0 = vec3.create(); + var ray1 = vec3.create(); + return function(x, y, out) { + out = out || vec3.create(); + x = (x - this.viewport[0]) / this.viewport[2]; + y = 1 - (y - this.viewport[1]) / this.viewport[3]; + vec3.lerp(ray0, this.ray00, this.ray10, x); + vec3.lerp(ray1, this.ray01, this.ray11, x); + vec3.lerp( out, ray0, ray1, y) + return vec3.normalize( out, out ); + } +})(); + +// ### GL.Raytracer.hitTestBox(origin, ray, min, max) +// +// Traces the ray starting from `origin` along `ray` against the axis-aligned box +// whose coordinates extend from `min` to `max`. Returns a `HitTest` with the +// information or `null` for no intersection. +// +// This implementation uses the [slab intersection method](http://www.siggraph.org/education/materials/HyperGraph/raytrace/rtinter3.htm). +var _hittest_inv = mat4.create(); +Raytracer.hitTestBox = function(origin, ray, min, max, model) { + var _hittest_v3 = new Float32Array(10*3); //reuse memory to speedup + + if(model) + { + var inv = mat4.invert( _hittest_inv, model ); + origin = mat4.multiplyVec3( _hittest_v3.subarray(3,6), inv, origin ); + ray = mat4.rotateVec3( _hittest_v3.subarray(6,9), inv, ray ); + } + + var tMin = vec3.subtract( _hittest_v3.subarray(9,12), min, origin ); + vec3.divide( tMin, tMin, ray ); + + var tMax = vec3.subtract( _hittest_v3.subarray(12,15), max, origin ); + vec3.divide( tMax, tMax, ray ); + + var t1 = vec3.min( _hittest_v3.subarray(15,18), tMin, tMax); + var t2 = vec3.max( _hittest_v3.subarray(18,21), tMin, tMax); + + var tNear = vec3.maxValue(t1); + var tFar = vec3.minValue(t2); + + if (tNear > 0 && tNear <= tFar) { + var epsilon = 1.0e-6; + var hit = vec3.scale( _hittest_v3.subarray(21,24), ray, tNear); + vec3.add( hit, origin, hit ); + + vec3.addValue(_hittest_v3.subarray(24,27), min, epsilon); + vec3.subValue(_hittest_v3.subarray(27,30), max, epsilon); + + return new HitTest(tNear, hit, vec3.fromValues( + (hit[0] > max[0]) - (hit[0] < min[0]), + (hit[1] > max[1]) - (hit[1] < min[1]), + (hit[2] > max[2]) - (hit[2] < min[2]) + )); + } + + return null; +}; + + + + +// ### GL.Raytracer.hitTestSphere(origin, ray, center, radius) +// +// Traces the ray starting from `origin` along `ray` against the sphere defined +// by `center` and `radius`. Returns a `HitTest` with the information or `null` +// for no intersection. +Raytracer.hitTestSphere = function(origin, ray, center, radius) { + var offset = vec3.subtract( vec3.create(), origin,center); + var a = vec3.dot(ray,ray); + var b = 2 * vec3.dot(ray,offset); + var c = vec3.dot(offset,offset) - radius * radius; + var discriminant = b * b - 4 * a * c; + + if (discriminant > 0) { + var t = (-b - Math.sqrt(discriminant)) / (2 * a), hit = vec3.add(vec3.create(),origin, vec3.scale(vec3.create(), ray, t)); + return new HitTest(t, hit, vec3.scale( vec3.create(), vec3.subtract(vec3.create(), hit,center), 1.0/radius)); + } + + return null; +}; + + +// ### GL.Raytracer.hitTestTriangle(origin, ray, a, b, c) +// +// Traces the ray starting from `origin` along `ray` against the triangle defined +// by the points `a`, `b`, and `c`. Returns a `HitTest` with the information or +// `null` for no intersection. +Raytracer.hitTestTriangle = function(origin, ray, a, b, c) { + var ab = vec3.subtract(vec3.create(), b,a ); + var ac = vec3.subtract(vec3.create(), c,a ); + var normal = vec3.cross( vec3.create(), ab,ac); + vec3.normalize( normal, normal ); + var t = vec3.dot(normal, vec3.subtract( vec3.create(), a,origin)) / vec3.dot(normal,ray); + + if (t > 0) { + var hit = vec3.add( vec3.create(), origin, vec3.scale(vec3.create(), ray,t)); + var toHit = vec3.subtract( vec3.create(), hit, a); + var dot00 = vec3.dot(ac,ac); + var dot01 = vec3.dot(ac,ab); + var dot02 = vec3.dot(ac,toHit); + var dot11 = vec3.dot(ab,ab); + var dot12 = vec3.dot(ab,toHit); + var divide = dot00 * dot11 - dot01 * dot01; + var u = (dot11 * dot02 - dot01 * dot12) / divide; + var v = (dot00 * dot12 - dot01 * dot02) / divide; + if (u >= 0 && v >= 0 && u + v <= 1) return new HitTest(t, hit, normal); + } + + return null; +}; +//***** OBJ parser adapted from SpiderGL implementation ***************** +/** +* Parses a OBJ string and returns an object with the info ready to be passed to GL.Mesh.load +* @method Mesh.parseOBJ +* @param {String} data all the OBJ info to be parsed +* @param {Object} options +* @return {Object} mesh information (vertices, coords, normals, indices) +*/ + +Mesh.parseOBJ = function( text, options ) +{ + options = options || {}; + + //final arrays (packed, lineal [ax,ay,az, bx,by,bz ...]) + var positionsArray = [ ]; + var texcoordsArray = [ ]; + var normalsArray = [ ]; + var indicesArray = [ ]; + + //unique arrays (not packed, lineal) + var positions = [ ]; + var texcoords = [ ]; + var normals = [ ]; + var facemap = { }; + var index = 0; + + var line = null; + var f = null; + var pos = 0; + var tex = 0; + var nor = 0; + var x = 0.0; + var y = 0.0; + var z = 0.0; + var tokens = null; + + var hasPos = false; + var hasTex = false; + var hasNor = false; + + var parsingFaces = false; + var indices_offset = 0; + var negative_offset = -1; //used for weird objs with negative indices + var max_index = 0; + + var skip_indices = options.noindex ? options.noindex : (text.length > 10000000 ? true : false); + //trace("SKIP INDICES: " + skip_indices); + var flip_axis = options.flipAxis; + var flip_normals = (flip_axis || options.flipNormals); + + //used for mesh groups (submeshes) + var group = null; + var groups = []; + var materials_found = {}; + + var V_CODE = 1; + var VT_CODE = 2; + var VN_CODE = 3; + var F_CODE = 4; + var G_CODE = 5; + var O_CODE = 6; + var codes = { v: V_CODE, vt: VT_CODE, vn: VN_CODE, f: F_CODE, g: G_CODE, o: O_CODE }; + + + var lines = text.split("\n"); + var length = lines.length; + for (var lineIndex = 0; lineIndex < length; ++lineIndex) { + line = lines[lineIndex].replace(/[ \t]+/g, " ").replace(/\s\s*$/, ""); //trim + + if (line[0] == "#") continue; + if(line == "") continue; + + tokens = line.split(" "); + var code = codes[ tokens[0] ]; + + if(parsingFaces && code == V_CODE) //another mesh? + { + indices_offset = index; + parsingFaces = false; + //trace("multiple meshes: " + indices_offset); + } + + //read and parse numbers + if( code <= VN_CODE ) //v,vt,vn + { + x = parseFloat(tokens[1]); + y = parseFloat(tokens[2]); + if( code != VT_CODE ) + { + if(tokens[3] == '\\') //super weird case, OBJ allows to break lines with slashes... + { + //HACK! only works if the var is the thirth position... + ++lineIndex; + line = lines[lineIndex].replace(/[ \t]+/g, " ").replace(/\s\s*$/, ""); //better than trim + z = parseFloat(line); + } + else + z = parseFloat(tokens[3]); + } + } + + if (code == V_CODE) { + if(flip_axis) //maya and max notation style + positions.push(-1*x,z,y); + else + positions.push(x,y,z); + } + else if (code == VT_CODE) { + texcoords.push(x,y); + } + else if (code == VN_CODE) { + + if(flip_normals) //maya and max notation style + normals.push(-y,-z,x); + else + normals.push(x,y,z); + } + else if (code == F_CODE) { + parsingFaces = true; + + if (tokens.length < 4) continue; //faces with less that 3 vertices? nevermind + + //for every corner of this polygon + var polygon_indices = []; + for (var i=1; i < tokens.length; ++i) + { + if (!(tokens[i] in facemap) || skip_indices) + { + f = tokens[i].split("/"); + + if (f.length == 1) { //unpacked + pos = parseInt(f[0]) - 1; + tex = pos; + nor = pos; + } + else if (f.length == 2) { //no normals + pos = parseInt(f[0]) - 1; + tex = parseInt(f[1]) - 1; + nor = -1; + } + else if (f.length == 3) { //all three indexed + pos = parseInt(f[0]) - 1; + tex = parseInt(f[1]) - 1; + nor = parseInt(f[2]) - 1; + } + else { + console.err("Problem parsing: unknown number of values per face"); + return false; + } + + if(i > 3 && skip_indices) //break polygon in triangles + { + //first + var pl = positionsArray.length; + positionsArray.push( positionsArray[pl - (i-3)*9], positionsArray[pl - (i-3)*9 + 1], positionsArray[pl - (i-3)*9 + 2]); + positionsArray.push( positionsArray[pl - 3], positionsArray[pl - 2], positionsArray[pl - 1]); + pl = texcoordsArray.length; + texcoordsArray.push( texcoordsArray[pl - (i-3)*6], texcoordsArray[pl - (i-3)*6 + 1]); + texcoordsArray.push( texcoordsArray[pl - 2], texcoordsArray[pl - 1]); + pl = normalsArray.length; + normalsArray.push( normalsArray[pl - (i-3)*9], normalsArray[pl - (i-3)*9 + 1], normalsArray[pl - (i-3)*9 + 2]); + normalsArray.push( normalsArray[pl - 3], normalsArray[pl - 2], normalsArray[pl - 1]); + } + + //add new vertex + x = 0.0; + y = 0.0; + z = 0.0; + if ((pos * 3 + 2) < positions.length) { + hasPos = true; + x = positions[pos*3+0]; + y = positions[pos*3+1]; + z = positions[pos*3+2]; + } + positionsArray.push(x,y,z); + + //add new texture coordinate + x = 0.0; + y = 0.0; + if ((tex * 2 + 1) < texcoords.length) { + hasTex = true; + x = texcoords[tex*2+0]; + y = texcoords[tex*2+1]; + } + texcoordsArray.push(x,y); + + //add new normal + x = 0.0; + y = 0.0; + z = 1.0; + if(nor != -1) + { + if ((nor * 3 + 2) < normals.length) { + hasNor = true; + x = normals[nor*3+0]; + y = normals[nor*3+1]; + z = normals[nor*3+2]; + } + + normalsArray.push(x,y,z); + } + + //Save the string "10/10/10" and tells which index represents it in the arrays + if(!skip_indices) + facemap[tokens[i]] = index++; + }//end of 'if this token is new (store and index for later reuse)' + + //store key for this triplet + if(!skip_indices) + { + var final_index = facemap[tokens[i]]; + polygon_indices.push(final_index); + if(max_index < final_index) + max_index = final_index; + } + } //end of for every token on a 'f' line + + //polygons (not just triangles) + if(!skip_indices) + { + for(var iP = 2; iP < polygon_indices.length; iP++) + { + indicesArray.push( polygon_indices[0], polygon_indices[iP-1], polygon_indices[iP] ); + //indicesArray.push( [polygon_indices[0], polygon_indices[iP-1], polygon_indices[iP]] ); + } + } + } + else if (code == G_CODE) { //tokens[0] == "usemtl" + negative_offset = positions.length / 3 - 1; + + if(tokens.length > 1) + { + if(group != null) + { + group.length = indicesArray.length - group.start; + if(group.length > 0) + groups.push(group); + } + + group = { + name: tokens[1], + start: indicesArray.length, + length: -1, + material: "" + }; + } + } + else if (tokens[0] == "usemtl") { + if(group) + group.material = tokens[1]; + } + } + + if(!positions.length) + { + console.error("OBJ doesnt have vertices, maybe the file is not a OBJ"); + return null; + } + + if(group && (indicesArray.length - group.start) > 1) + { + group.length = indicesArray.length - group.start; + groups.push(group); + } + + //deindex streams + if((max_index > 256*256 || skip_indices ) && indicesArray.length > 0) + { + console.log("Deindexing mesh...") + var finalVertices = new Float32Array(indicesArray.length * 3); + var finalNormals = normalsArray && normalsArray.length ? new Float32Array(indicesArray.length * 3) : null; + var finalTexCoords = texcoordsArray && texcoordsArray.length ? new Float32Array(indicesArray.length * 2) : null; + for(var i = 0; i < indicesArray.length; i += 1) + { + finalVertices.set( positionsArray.slice( indicesArray[i]*3,indicesArray[i]*3 + 3), i*3 ); + if(finalNormals) + finalNormals.set( normalsArray.slice( indicesArray[i]*3,indicesArray[i]*3 + 3 ), i*3 ); + if(finalTexCoords) + finalTexCoords.set( texcoordsArray.slice(indicesArray[i]*2,indicesArray[i]*2 + 2 ), i*2 ); + } + positionsArray = finalVertices; + if(finalNormals) + normalsArray = finalNormals; + if(finalTexCoords) + texcoordsArray = finalTexCoords; + indicesArray = null; + } + + //Create final mesh object + var mesh = {}; + + //create typed arrays + if (hasPos) + mesh.vertices = new Float32Array(positionsArray); + if (hasNor && normalsArray.length > 0) + mesh.normals = new Float32Array(normalsArray); + if (hasTex && texcoordsArray.length > 0) + mesh.coords = new Float32Array(texcoordsArray); + if (indicesArray && indicesArray.length > 0) + mesh.triangles = new Uint16Array(indicesArray); + + var info = {}; + if(groups.length > 1) + info.groups = groups; + mesh.info = info; + + if(options.only_data) + return mesh; + + //creates and returns a GL.Mesh + var final_mesh = null; + final_mesh = Mesh.load( mesh, null, options.mesh ); + final_mesh.updateBoundingBox(); + return final_mesh; +} + +Mesh.parsers["obj"] = Mesh.parseOBJ; + +Mesh.encoders["obj"] = function( mesh, options ) +{ + //store vertices + var verticesBuffer = mesh.getBuffer("vertices"); + if(!verticesBuffer) + return null; + + var lines = []; + lines.push("# Generated with liteGL.js by Javi Agenjo\n"); + + var vertices = verticesBuffer.data; + for (var i = 0; i < vertices.length; i+=3) + lines.push("v " + vertices[i].toFixed(4) + " " + vertices[i+1].toFixed(4) + " " + vertices[i+2].toFixed(4)); + + //store normals + var normalsBuffer = mesh.getBuffer("normals"); + if(normalsBuffer) + { + lines.push(""); + var normals = normalsBuffer.data; + for (var i = 0; i < normals.length; i+=3) + lines.push("vn " + normals[i].toFixed(4) + " " + normals[i+1].toFixed(4) + " " + normals[i+2].toFixed(4) ); + } + + //store uvs + var coordsBuffer = mesh.getBuffer("coords"); + if(coordsBuffer) + { + lines.push(""); + var coords = coordsBuffer.data; + for (var i = 0; i < coords.length; i+=2) + lines.push("vt " + coords[i].toFixed(4) + " " + coords[i+1].toFixed(4) + " " + " 0.0000"); + } + + var groups = mesh.info.groups; + + + //store faces + var indicesBuffer = mesh.getIndexBuffer("triangles"); + if(indicesBuffer) + { + var indices = indicesBuffer.data; + if(!groups || !groups.length) + groups = [{start:0, length: indices.length, name:"mesh"}]; + for(var j = 0; j < groups.length; ++j) + { + var group = groups[j]; + lines.push("g " + group.name ); + lines.push("usemtl " + (group.material || ("mat_"+j))); + var start = group.start; + var end = start + group.length; + for (var i = start; i < end; i+=3) + lines.push("f " + (indices[i]+1) + "/" + (indices[i]+1) + "/" + (indices[i]+1) + " " + (indices[i+1]+1) + "/" + (indices[i+1]+1) + "/" + (indices[i+1]+1) + " " + (indices[i+2]+1) + "/" + (indices[i+2]+1) + "/" + (indices[i+2]+1) ); + } + } + else //no indices + { + if(!groups || !groups.length) + groups = [{start:0, length: (vertices.length / 3), name:"mesh"}]; + for(var j = 0; j < groups.length; ++j) + { + var group = groups[j]; + lines.push("g " + group.name); + lines.push("usemtl " + (group.material || ("mat_"+j))); + var start = group.start; + var end = start + group.length; + for (var i = start; i < end; i+=3) + lines.push( "f " + (i+1) + "/" + (i+1) + "/" + (i+1) + " " + (i+2) + "/" + (i+2) + "/" + (i+2) + " " + (i+3) + "/" + (i+3) + "/" + (i+3) ); + } + } + + return lines.join("\n"); +} + +//simple format to output meshes in ASCII +Mesh.parsers["mesh"] = function( text, options ) +{ + var mesh = {}; + + var lines = text.split("\n"); + for(var i = 0; i < lines.length; ++i) + { + var line = lines[i]; + var type = line[0]; + var t = line.substr(1).split(","); + var name = t[0]; + + if(type == "-") //buffer + { + var data = new Float32Array( Number(t[1]) ); + for(var j = 0; j < data.length; ++j) + data[j] = Number(t[j+2]); + mesh[name] = data; + } + else if(type == "*") //index + { + var data = Number(t[1]) > 256*256 ? new Uint32Array( Number(t[1]) ) : new Uint16Array( Number(t[1]) ); + for(var j = 0; j < data.length; ++j) + data[j] = Number(t[j+2]); + mesh[name] = data; + } + else if(type == "@") //info + { + if(name == "bones") + { + var bones = []; + var num_bones = Number(t[1]); + for(var j = 0; j < num_bones; ++j) + { + var m = (t.slice(3 + j*17, 3 + (j+1)*17 - 1)).map(Number); + bones.push( [ t[2 + j*17], m ] ); + } + mesh.bones = bones; + } + else if(name == "bind_matrix") + mesh.bind_matrix = t.slice(1,17).map(Number); + else if(name == "groups") + { + mesh.info = { groups: [] }; + var num_groups = Number(t[1]); + for(var j = 0; j < num_groups; ++j) + { + var group = { name: t[2+j*4], material: t[2+j*4+1], start: Number(t[2+j*4+2]), length: Number(t[2+j*4+3]) }; + mesh.info.groups.push(group); + } + } + } + else + console.warn("type unknown: " + t[0] ); + } + + if(options.only_data) + return mesh; + + //creates and returns a GL.Mesh + var final_mesh = null; + final_mesh = Mesh.load( mesh, null, options.mesh ); + final_mesh.updateBoundingBox(); + return final_mesh; +} + +Mesh.encoders["mesh"] = function( mesh, options ) +{ + var lines = []; + for(var i in mesh.vertexBuffers ) + { + var buffer = mesh.vertexBuffers[i]; + var line = ["-"+i, buffer.data.length, buffer.data, typedArrayToArray( buffer.data ) ]; + lines.push(line.join(",")); + } + + for(var i in mesh.indexBuffers ) + { + var buffer = mesh.indexBuffers[i]; + var line = [ "*" + i, buffer.data.length, buffer.data, typedArrayToArray( buffer.data ) ]; + lines.push(line.join(",")); + } + + if(mesh.bounding) + lines.push( ["@bounding", typedArrayToArray(mesh.bounding.subarray(0,6))].join(",") ); + if(mesh.info && mesh.info.groups) + { + var groups_info = []; + for(var j = 0; j < mesh.info.groups.length; ++j) + { + var group = mesh.info.groups[j]; + groups_info.push( group.name, group.material, group.start, group.length ); + } + lines.push( ["@groups", mesh.info.groups.length ].concat( groups_info ).join(",") ); + } + + if(mesh.bones) + lines.push( ["@bones", mesh.bones.length, mesh.bones.flat()].join(",") ); + if(mesh.bind_matrix) + lines.push( ["@bind_matrix", typedArrayToArray(mesh.bind_matrix) ].join(",") ); + + return lines.join("\n"); +} + +/* BINARY FORMAT ************************************/ + +if(global.WBin) + global.WBin.classes["Mesh"] = Mesh; + +Mesh.binary_file_formats["wbin"] = true; + +Mesh.parsers["wbin"] = Mesh.fromBinary = function( data_array, options ) +{ + if(!global.WBin) + throw("To use binary meshes you need to install WBin.js from https://github.com/jagenjo/litescene.js/blob/master/src/utils/wbin.js "); + + options = options || {}; + + var o = null; + if( data_array.constructor == ArrayBuffer ) + o = WBin.load( data_array, true ); + else + o = data_array; + + if(!o.info) + console.warn("This WBin doesn't seem to contain a mesh. Classname: ", o["@classname"] ); + + if( o.format ) + GL.Mesh.decompress( o ); + + var vertex_buffers = {}; + if(o.vertex_buffers) + { + for(var i in o.vertex_buffers) + vertex_buffers[ o.vertex_buffers[i] ] = o[ o.vertex_buffers[i] ]; + } + else + { + if(o.vertices) vertex_buffers.vertices = o.vertices; + if(o.normals) vertex_buffers.normals = o.normals; + if(o.coords) vertex_buffers.coords = o.coords; + if(o.weights) vertex_buffers.weights = o.weights; + if(o.bone_indices) vertex_buffers.bone_indices = o.bone_indices; + } + + var index_buffers = {}; + if( o.index_buffers ) + { + for(var i in o.index_buffers) + index_buffers[ o.index_buffers[i] ] = o[ o.index_buffers[i] ]; + } + else + { + if(o.triangles) index_buffers.triangles = o.triangles; + if(o.wireframe) index_buffers.wireframe = o.wireframe; + } + + var mesh = { + vertex_buffers: vertex_buffers, + index_buffers: index_buffers, + bounding: o.bounding, + info: o.info + }; + + if(o.bones) + { + mesh.bones = o.bones; + //restore Float32array + for(var i = 0; i < mesh.bones.length; ++i) + mesh.bones[i][1] = mat4.clone(mesh.bones[i][1]); + if(o.bind_matrix) + mesh.bind_matrix = mat4.clone( o.bind_matrix ); + } + + if(o.morph_targets) + mesh.morph_targets = o.morph_targets; + + if(options.only_data) + return mesh; + + //build mesh object + var final_mesh = options.mesh || new GL.Mesh(); + final_mesh.configure( mesh ); + return final_mesh; +} + +Mesh.encoders["wbin"] = function( mesh, options ) +{ + return mesh.toBinary( options ); +} + +Mesh.prototype.toBinary = function( options ) +{ + if(!global.WBin) + throw("to use Mesh.toBinary you need to have WBin included. Check the repository for wbin.js"); + + if(!this.info) + this.info = {}; + + //clean data + var o = { + object_class: "Mesh", + info: this.info, + groups: this.groups + }; + + if(this.bones) + { + var bones = []; + //convert to array + for(var i = 0; i < this.bones.length; ++i) + bones.push([ this.bones[i][0], mat4.toArray( this.bones[i][1] ) ]); + o.bones = bones; + if(this.bind_matrix) + o.bind_matrix = this.bind_matrix; + } + + //bounding box + if(!this.bounding) + this.updateBoundingBox(); + o.bounding = this.bounding; + + var vertex_buffers = []; + var index_buffers = []; + + for(var i in this.vertexBuffers) + { + var stream = this.vertexBuffers[i]; + o[ stream.name ] = stream.data; + vertex_buffers.push( stream.name ); + + if(stream.name == "vertices") + o.info.num_vertices = stream.data.length / 3; + } + + for(var i in this.indexBuffers) + { + var stream = this.indexBuffers[i]; + o[i] = stream.data; + index_buffers.push( i ); + } + + o.vertex_buffers = vertex_buffers; + o.index_buffers = index_buffers; + + //compress wbin using the bounding + if( GL.Mesh.enable_wbin_compression ) //apply compression + GL.Mesh.compress( o ); + + //create pack file + var bin = WBin.create( o, "Mesh" ); + return bin; +} + +Mesh.compress = function( o, format ) +{ + format = format || "bounding_compressed"; + o.format = { + type: format + }; + + var func = Mesh.compressors[ format ]; + if(!func) + throw("compression format not supported:" + format ); + return func( o ); +} + +Mesh.decompress = function( o ) +{ + if(!o.format) + return; + var func = Mesh.decompressors[ o.format.type ]; + if(!func) + throw("decompression format not supported:" + o.format.type ); + return func( o ); +} + +Mesh.compressors["bounding_compressed"] = function(o) +{ + if(!o.vertex_buffers) + throw("buffers not found"); + + var min = BBox.getMin( o.bounding ); + var max = BBox.getMax( o.bounding ); + var range = vec3.sub( vec3.create(), max, min ); + + var vertices = o.vertices; + var new_vertices = new Uint16Array( vertices.length ); + for(var i = 0; i < vertices.length; i+=3) + { + new_vertices[i] = ((vertices[i] - min[0]) / range[0]) * 65535; + new_vertices[i+1] = ((vertices[i+1] - min[1]) / range[1]) * 65535; + new_vertices[i+2] = ((vertices[i+2] - min[2]) / range[2]) * 65535; + } + o.vertices = new_vertices; + + if( o.normals ) + { + var normals = o.normals; + var new_normals = new Uint8Array( normals.length ); + var normals_range = new_normals.constructor == Uint8Array ? 255 : 65535; + for(var i = 0; i < normals.length; i+=3) + { + new_normals[i] = (normals[i] * 0.5 + 0.5) * normals_range; + new_normals[i+1] = (normals[i+1] * 0.5 + 0.5) * normals_range; + new_normals[i+2] = (normals[i+2] * 0.5 + 0.5) * normals_range; + } + o.normals = new_normals; + } + + if( o.coords ) + { + //compute uv bounding: [minu,minv,maxu,maxv] + var coords = o.coords; + var uvs_bounding = [10000,10000,-10000,-10000]; + for(var i = 0; i < coords.length; i+=2) + { + var u = coords[i]; + if( uvs_bounding[0] > u ) uvs_bounding[0] = u; + else if( uvs_bounding[2] < u ) uvs_bounding[2] = u; + var v = coords[i+1]; + if( uvs_bounding[1] > v ) uvs_bounding[1] = v; + else if( uvs_bounding[3] < v ) uvs_bounding[3] = v; + } + o.format.uvs_bounding = uvs_bounding; + + var new_coords = new Uint16Array( coords.length ); + var range = [ uvs_bounding[2] - uvs_bounding[0], uvs_bounding[3] - uvs_bounding[1] ]; + for(var i = 0; i < coords.length; i+=2) + { + new_coords[i] = ((coords[i] - uvs_bounding[0]) / range[0]) * 65535; + new_coords[i+1] = ((coords[i+1] - uvs_bounding[1]) / range[1]) * 65535; + } + o.coords = new_coords; + } + + if( o.weights ) + { + var weights = o.weights; + var new_weights = new Uint16Array( weights.length ); //using only one byte distorts the meshes a lot + var weights_range = new_weights.constructor == Uint8Array ? 255 : 65535; + for(var i = 0; i < weights.length; i+=4) + { + new_weights[i] = weights[i] * weights_range; + new_weights[i+1] = weights[i+1] * weights_range; + new_weights[i+2] = weights[i+2] * weights_range; + new_weights[i+3] = weights[i+3] * weights_range; + } + o.weights = new_weights; + } +} + + +Mesh.decompressors["bounding_compressed"] = function(o) +{ + var bounding = o.bounding; + if(!bounding) + throw("error in mesh decompressing data: bounding not found, cannot use the bounding decompression."); + + var min = BBox.getMin( bounding ); + var max = BBox.getMax( bounding ); + var range = vec3.sub( vec3.create(), max, min ); + + var format = o.format; + + var inv8 = 1 / 255; + var inv16 = 1 / 65535; + var vertices = o.vertices; + var new_vertices = new Float32Array( vertices.length ); + for( var i = 0, l = vertices.length; i < l; i += 3 ) + { + new_vertices[i] = ((vertices[i] * inv16) * range[0]) + min[0]; + new_vertices[i+1] = ((vertices[i+1] * inv16) * range[1]) + min[1]; + new_vertices[i+2] = ((vertices[i+2] * inv16) * range[2]) + min[2]; + } + o.vertices = new_vertices; + + if( o.normals && o.normals.constructor != Float32Array ) + { + var normals = o.normals; + var new_normals = new Float32Array( normals.length ); + var inormals_range = normals.constructor == Uint8Array ? inv8 : inv16; + for( var i = 0, l = normals.length; i < l; i += 3 ) + { + new_normals[i] = (normals[i] * inormals_range) * 2.0 - 1.0; + new_normals[i+1] = (normals[i+1] * inormals_range) * 2.0 - 1.0; + new_normals[i+2] = (normals[i+2] * inormals_range) * 2.0 - 1.0; + var N = new_normals.subarray(i,i+3); + vec3.normalize(N,N); + } + o.normals = new_normals; + } + + if( o.coords && format.uvs_bounding && o.coords.constructor != Float32Array ) + { + var coords = o.coords; + var uvs_bounding = format.uvs_bounding; + var range = [ uvs_bounding[2] - uvs_bounding[0], uvs_bounding[3] - uvs_bounding[1] ]; + var new_coords = new Float32Array( coords.length ); + for( var i = 0, l = coords.length; i < l; i += 2 ) + { + new_coords[i] = (coords[i] * inv16) * range[0] + uvs_bounding[0]; + new_coords[i+1] = (coords[i+1] * inv16) * range[1] + uvs_bounding[1]; + } + o.coords = new_coords; + } + + //bones are already in Uint8 format so dont need to compress them further, but weights yes + if( o.weights && o.weights.constructor != Float32Array ) //do we really need to unpack them? what if we use them like this? + { + var weights = o.weights; + var new_weights = new Float32Array( weights.length ); + var iweights_range = weights.constructor == Uint8Array ? inv8 : inv16; + for(var i = 0, l = weights.length; i < l; i += 4 ) + { + new_weights[i] = weights[i] * iweights_range; + new_weights[i+1] = weights[i+1] * iweights_range; + new_weights[i+2] = weights[i+2] * iweights_range; + new_weights[i+3] = weights[i+3] * iweights_range; + } + o.weights = new_weights; + } +} + +//footer.js +})( typeof(window) != "undefined" ? window : (typeof(self) != "undefined" ? self : global ) ); diff --git a/editor/js/libs/midi-parser.js b/editor/js/libs/midi-parser.js new file mode 100644 index 000000000..6d0d53de2 --- /dev/null +++ b/editor/js/libs/midi-parser.js @@ -0,0 +1,356 @@ +/* + Project Name : midi-parser-js + Project Url : https://github.com/colxi/midi-parser-js/ + Author : colxi + Author URL : http://www.colxi.info/ + Description : MidiParser library reads .MID binary files, Base64 encoded MIDI Data, + or UInt8 Arrays, and outputs as a readable and structured JS object. +*/ + +(function(){ + 'use strict'; + + /** + * CROSSBROWSER & NODEjs POLYFILL for ATOB() - + * By: https://github.com/MaxArt2501 (modified) + * @param {string} string [description] + * @return {[type]} [description] + */ + const _atob = function(string) { + // base64 character set, plus padding character (=) + let b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; + // Regular expression to check formal correctness of base64 encoded strings + let b64re = /^(?:[A-Za-z\d+\/]{4})*?(?:[A-Za-z\d+\/]{2}(?:==)?|[A-Za-z\d+\/]{3}=?)?$/; + // remove data type signatures at the begining of the string + // eg : "data:audio/mid;base64," + string = string.replace( /^.*?base64,/ , ''); + // atob can work with strings with whitespaces, even inside the encoded part, + // but only \t, \n, \f, \r and ' ', which can be stripped. + string = String(string).replace(/[\t\n\f\r ]+/g, ''); + if (!b64re.test(string)) + throw new TypeError('Failed to execute _atob() : The string to be decoded is not correctly encoded.'); + + // Adding the padding if missing, for semplicity + string += '=='.slice(2 - (string.length & 3)); + let bitmap, result = ''; + let r1, r2, i = 0; + for (; i < string.length;) { + bitmap = b64.indexOf(string.charAt(i++)) << 18 | b64.indexOf(string.charAt(i++)) << 12 + | (r1 = b64.indexOf(string.charAt(i++))) << 6 | (r2 = b64.indexOf(string.charAt(i++))); + + result += r1 === 64 ? String.fromCharCode(bitmap >> 16 & 255) + : r2 === 64 ? String.fromCharCode(bitmap >> 16 & 255, bitmap >> 8 & 255) + : String.fromCharCode(bitmap >> 16 & 255, bitmap >> 8 & 255, bitmap & 255); + } + return result; + }; + + + /** + * [MidiParser description] + * @type {Object} + */ + const MidiParser = { + // debug (bool), when enabled will log in console unimplemented events + // warnings and internal handled errors. + debug: false, + + /** + * [parse description] + * @param {[type]} input [description] + * @param {[type]} _callback [description] + * @return {[type]} [description] + */ + parse: function(input, _callback){ + if(input instanceof Uint8Array) return MidiParser.Uint8(input); + else if(typeof input === 'string') return MidiParser.Base64(input); + else if(input instanceof HTMLElement && input.type === 'file') return MidiParser.addListener(input , _callback); + else throw new Error('MidiParser.parse() : Invalid input provided'); + }, + + /** + * addListener() should be called in order attach a listener to the INPUT HTML element + * that will provide the binary data automating the conversion, and returning + * the structured data to the provided callback function. + */ + addListener: function(_fileElement, _callback){ + if(!File || !FileReader) throw new Error('The File|FileReader APIs are not supported in this browser. Use instead MidiParser.Base64() or MidiParser.Uint8()'); + + // validate provided element + if( _fileElement === undefined || + !(_fileElement instanceof HTMLElement) || + _fileElement.tagName !== 'INPUT' || + _fileElement.type.toLowerCase() !== 'file' + ){ + console.warn('MidiParser.addListener() : Provided element is not a valid FILE INPUT element'); + return false; + } + _callback = _callback || function(){}; + + _fileElement.addEventListener('change', function(InputEvt){ // set the 'file selected' event handler + if (!InputEvt.target.files.length) return false; // return false if no elements where selected + console.log('MidiParser.addListener() : File detected in INPUT ELEMENT processing data..'); + let reader = new FileReader(); // prepare the file Reader + reader.readAsArrayBuffer(InputEvt.target.files[0]); // read the binary data + reader.onload = function(e){ + _callback( MidiParser.Uint8(new Uint8Array(e.target.result))); // encode data with Uint8Array and call the parser + }; + }); + }, + + /** + * Base64() : convert baset4 string into uint8 array buffer, before performing the + * parsing subroutine. + */ + Base64 : function(b64String){ + b64String = String(b64String); + + let raw = _atob(b64String); + let rawLength = raw.length; + let t_array = new Uint8Array(new ArrayBuffer(rawLength)); + + for(let i=0; i 1){ + for(let i=1; i<= (_bytes-1); i++){ + value += this.data.getUint8(this.pointer) * Math.pow(256, (_bytes - i)); + this.pointer++; + } + } + value += this.data.getUint8(this.pointer); + this.pointer++; + return value; + }, + readStr: function(_bytes){ // read as ASCII chars, the followoing _bytes + let text = ''; + for(let char=1; char <= _bytes; char++) text += String.fromCharCode(this.readInt(1)); + return text; + }, + readIntVLV: function(){ // read a variable length value + let value = 0; + if ( this.pointer >= this.data.byteLength ){ + return -1; // EOF + }else if(this.data.getUint8(this.pointer) < 128){ // ...value in a single byte + value = this.readInt(1); + }else{ // ...value in multiple bytes + let FirstBytes = []; + while(this.data.getUint8(this.pointer) >= 128){ + FirstBytes.push(this.readInt(1) - 128); + } + let lastByte = this.readInt(1); + for(let dt = 1; dt <= FirstBytes.length; dt++){ + value += FirstBytes[FirstBytes.length - dt] * Math.pow(128, dt); + } + value += lastByte; + } + return value; + } + }; + + file.data = new DataView(FileAsUint8Array.buffer, FileAsUint8Array.byteOffset, FileAsUint8Array.byteLength); // 8 bits bytes file data array + // ** read FILE HEADER + if(file.readInt(4) !== 0x4D546864){ + console.warn('Header validation failed (not MIDI standard or file corrupt.)'); + return false; // Header validation failed (not MIDI standard or file corrupt.) + } + let headerSize = file.readInt(4); // header size (unused var), getted just for read pointer movement + let MIDI = {}; // create new midi object + MIDI.formatType = file.readInt(2); // get MIDI Format Type + MIDI.tracks = file.readInt(2); // get ammount of track chunks + MIDI.track = []; // create array key for track data storing + let timeDivisionByte1 = file.readInt(1); // get Time Division first byte + let timeDivisionByte2 = file.readInt(1); // get Time Division second byte + if(timeDivisionByte1 >= 128){ // discover Time Division mode (fps or tpf) + MIDI.timeDivision = []; + MIDI.timeDivision[0] = timeDivisionByte1 - 128; // frames per second MODE (1st byte) + MIDI.timeDivision[1] = timeDivisionByte2; // ticks in each frame (2nd byte) + }else MIDI.timeDivision = (timeDivisionByte1 * 256) + timeDivisionByte2;// else... ticks per beat MODE (2 bytes value) + + // ** read TRACK CHUNK + for(let t=1; t <= MIDI.tracks; t++){ + MIDI.track[t-1] = {event: []}; // create new Track entry in Array + let headerValidation = file.readInt(4); + if ( headerValidation === -1 ) break; // EOF + if(headerValidation !== 0x4D54726B) return false; // Track chunk header validation failed. + file.readInt(4); // move pointer. get chunk size (bytes length) + let e = 0; // init event counter + let endOfTrack = false; // FLAG for track reading secuence breaking + // ** read EVENT CHUNK + let statusByte; + let laststatusByte; + while(!endOfTrack){ + e++; // increase by 1 event counter + MIDI.track[t-1].event[e-1] = {}; // create new event object, in events array + MIDI.track[t-1].event[e-1].deltaTime = file.readIntVLV(); // get DELTA TIME OF MIDI event (Variable Length Value) + statusByte = file.readInt(1); // read EVENT TYPE (STATUS BYTE) + if(statusByte === -1) break; // EOF + else if(statusByte >= 128) laststatusByte = statusByte; // NEW STATUS BYTE DETECTED + else{ // 'RUNNING STATUS' situation detected + statusByte = laststatusByte; // apply last loop, Status Byte + file.movePointer(-1); // move back the pointer (cause readed byte is not status byte) + } + + + // + // ** IS META EVENT + // + if(statusByte === 0xFF){ // Meta Event type + MIDI.track[t-1].event[e-1].type = 0xFF; // assign metaEvent code to array + MIDI.track[t-1].event[e-1].metaType = file.readInt(1); // assign metaEvent subtype + let metaEventLength = file.readIntVLV(); // get the metaEvent length + switch(MIDI.track[t-1].event[e-1].metaType){ + case 0x2F: // end of track, has no data byte + case -1: // EOF + endOfTrack = true; // change FLAG to force track reading loop breaking + break; + case 0x01: // Text Event + case 0x02: // Copyright Notice + case 0x03: + case 0x04: // Instrument Name + case 0x05: // Lyrics) + case 0x07: // Cue point // Sequence/Track Name (documentation: http://www.ta7.de/txt/musik/musi0006.htm) + case 0x06: // Marker + MIDI.track[t-1].event[e-1].data = file.readStr(metaEventLength); + break; + case 0x21: // MIDI PORT + case 0x59: // Key Signature + case 0x51: // Set Tempo + MIDI.track[t-1].event[e-1].data = file.readInt(metaEventLength); + break; + case 0x54: // SMPTE Offset + MIDI.track[t-1].event[e-1].data = []; + MIDI.track[t-1].event[e-1].data[0] = file.readInt(1); + MIDI.track[t-1].event[e-1].data[1] = file.readInt(1); + MIDI.track[t-1].event[e-1].data[2] = file.readInt(1); + MIDI.track[t-1].event[e-1].data[3] = file.readInt(1); + MIDI.track[t-1].event[e-1].data[4] = file.readInt(1); + break; + case 0x58: // Time Signature + MIDI.track[t-1].event[e-1].data = []; + MIDI.track[t-1].event[e-1].data[0] = file.readInt(1); + MIDI.track[t-1].event[e-1].data[1] = file.readInt(1); + MIDI.track[t-1].event[e-1].data[2] = file.readInt(1); + MIDI.track[t-1].event[e-1].data[3] = file.readInt(1); + break; + default : + // if user provided a custom interpreter, call it + // and assign to event the returned data + if( this.customInterpreter !== null){ + MIDI.track[t-1].event[e-1].data = this.customInterpreter( MIDI.track[t-1].event[e-1].metaType, file, metaEventLength); + } + // if no customInterpretr is provided, or returned + // false (=apply default), perform default action + if(this.customInterpreter === null || MIDI.track[t-1].event[e-1].data === false){ + file.readInt(metaEventLength); + MIDI.track[t-1].event[e-1].data = file.readInt(metaEventLength); + if (this.debug) console.info('Unimplemented 0xFF meta event! data block readed as Integer'); + } + } + } + + // + // IS REGULAR EVENT + // + else{ // MIDI Control Events OR System Exclusive Events + statusByte = statusByte.toString(16).split(''); // split the status byte HEX representation, to obtain 4 bits values + if(!statusByte[1]) statusByte.unshift('0'); // force 2 digits + MIDI.track[t-1].event[e-1].type = parseInt(statusByte[0], 16);// first byte is EVENT TYPE ID + MIDI.track[t-1].event[e-1].channel = parseInt(statusByte[1], 16);// second byte is channel + switch(MIDI.track[t-1].event[e-1].type){ + case 0xF:{ // System Exclusive Events + + // if user provided a custom interpreter, call it + // and assign to event the returned data + if( this.customInterpreter !== null){ + MIDI.track[t-1].event[e-1].data = this.customInterpreter( MIDI.track[t-1].event[e-1].type, file , false); + } + + // if no customInterpretr is provided, or returned + // false (=apply default), perform default action + if(this.customInterpreter === null || MIDI.track[t-1].event[e-1].data === false){ + let event_length = file.readIntVLV(); + MIDI.track[t-1].event[e-1].data = file.readInt(event_length); + if (this.debug) console.info('Unimplemented 0xF exclusive events! data block readed as Integer'); + } + break; + } + case 0xA: // Note Aftertouch + case 0xB: // Controller + case 0xE: // Pitch Bend Event + case 0x8: // Note off + case 0x9: // Note On + MIDI.track[t-1].event[e-1].data = []; + MIDI.track[t-1].event[e-1].data[0] = file.readInt(1); + MIDI.track[t-1].event[e-1].data[1] = file.readInt(1); + break; + case 0xC: // Program Change + case 0xD: // Channel Aftertouch + MIDI.track[t-1].event[e-1].data = file.readInt(1); + break; + case -1: // EOF + endOfTrack = true; // change FLAG to force track reading loop breaking + break; + default: + // if user provided a custom interpreter, call it + // and assign to event the returned data + if( this.customInterpreter !== null){ + MIDI.track[t-1].event[e-1].data = this.customInterpreter( MIDI.track[t-1].event[e-1].metaType, file , false); + } + + // if no customInterpretr is provided, or returned + // false (=apply default), perform default action + if(this.customInterpreter === null || MIDI.track[t-1].event[e-1].data === false){ + console.log('Unknown EVENT detected... reading cancelled!'); + return false; + } + } + } + } + } + return MIDI; + }, + + /** + * custom function to handle unimplemented, or custom midi messages. + * If message is a meta-event, the value of metaEventLength will be >0. + * Function must return the value to store, and pointer of dataView needs + * to be manually increased + * If you want default action to be performed, return false + */ + customInterpreter : null // function( e_type , arrayByffer, metaEventLength){ return e_data_int } + }; + + + // if running in NODE export module + if(typeof module !== 'undefined') module.exports = MidiParser; + else{ + // if running in Browser, set a global variable. + let _global = typeof window === 'object' && window.self === window && window || + typeof self === 'object' && self.self === self && self || + typeof global === 'object' && global.global === global && global; + + _global.MidiParser = MidiParser; + } + + + +})(); \ No newline at end of file diff --git a/demo/style.css b/editor/style.css similarity index 93% rename from demo/style.css rename to editor/style.css index ecf79c0e7..02f642f79 100755 --- a/demo/style.css +++ b/editor/style.css @@ -123,7 +123,7 @@ label { color: #AAF; } -input,textarea { +.header input { color: #EEE; background-color: #555; font-size: 1.2em; diff --git a/guides/README.md b/guides/README.md index 625ca2200..db26a2c27 100644 --- a/guides/README.md +++ b/guides/README.md @@ -12,7 +12,7 @@ And in ```the src/``` folder there is also another class included: ## LGraphNode LGraphNode is the base class used for all the nodes classes. -To extend the other classes all the methods contained in LGraphNode.prototype are copyed to the classes when registered. +To extend the other classes all the methods contained in LGraphNode.prototype are copied to the classes when registered. When you create a new node type you do not have to inherit from that class, when the node is registered all the methods are copied to your node prototype. This is done inside the functions ```LiteGraph.registerNodeType(...)```. @@ -59,12 +59,15 @@ LiteGraph.registerNodeType("basic/sum", MyAddNode ); There are several settings that could be defined or modified per node: * **size**: ```[width,height]``` the size of the area inside the node (excluding title). Every row is LiteGraph.NODE_SLOT_HEIGHT pixels height. * **properties**: object containing the properties that could be configured by the user, and serialized when saving the graph -* **shape**: the shape of the object (could be LiteGraph.BOX,LiteGraph.ROUND,LiteGraph.CARD) -* **flags**: several flags - * **resizable**: if it can be resized dragging the corner - * **horizontal**: if the slots should be placed horizontally on the top and bottom of the node - * **clip_area**: clips the content when rendering the node +* **shape**: the shape of the object (could be LiteGraph.BOX_SHAPE,LiteGraph.ROUND_SHAPE,LiteGraph.CARD_SHAPE) +* **flags**: flags that can be changed by the user and will be stored when serialized * **collapsed**: if it is shown collapsed (small) +* **redraw_on_mouse**: forces a redraw if the mouse passes over the widget +* **widgets_up**: widgets do not start after the slots +* **widgets_start_y**: widgets should start being drawn from this Y +* **clip_area**: clips the content when rendering the node +* **resizable**: if it can be resized dragging the corner +* **horizontal**: if the slots should be placed horizontally on the top and bottom of the node There are several callbacks that could be defined by the user: * **onAdded**: called when added to graph @@ -102,7 +105,7 @@ Slots have the next information: * **name**: string with the name of the slot (used also to show in the canvas) * **type**: string specifying the data type traveling through this link - * **link or links**: depending if the slot is input or ouput contains the id of the link or an array of ids + * **link or links**: depending if the slot is input or output contains the id of the link or an array of ids * **label**: optional, string used to rename the name as shown in the canvas. * **dir**: optional, could be LiteGraph.UP, LiteGraph.RIGHT, LiteGraph.DOWN, LiteGraph.LEFT * **color_on**: color to render when it is connected @@ -186,7 +189,14 @@ function MyNodeType() This is the list of supported widgets: * **"number"** to change a value of a number, the syntax is ```this.addWidget("number","Number", current_value, callback, { min: 0, max: 100, step: 1} );``` * **"slider"** to change a number by draging the mouse, the syntax is the same as number. -* **"combo"** to select between multiple choices, the syntax is: ```this.addWidget("combo","Combo", "red", callback, { values:["red","green","blue"]} );``` +* **"combo"** to select between multiple choices, the syntax is: + + ```this.addWidget("combo","Combo", "red", callback, { values:["red","green","blue"]} );``` + + or if you want to use objects: + + ```this.addWidget("combo","Combo", value1, callback, { values: { "title1":value1, "title2":value2 } );``` + * **"text"** to edit a short string * **"toggle"** like a checkbox * **"button"** @@ -244,3 +254,63 @@ If you want to start the graph then: ```js graph.start(); ``` + +## Events + +When we run a step in a graph (using ```graph.runStep()```) every node onExecute method will be called. +But sometimes you want that actions are only performed when some trigger is activated, for this situations you can use Events. + +Events allow to trigger executions in nodes only when an event is dispatched from one node. + +To define slots for nodes you must use the type LiteGraph.ACTION for inputs, and LIteGraph.EVENT for outputs: + +```js +function MyNode() +{ + this.addInput("play", LiteGraph.ACTION ); + this.addInput("onFinish", LiteGraph.EVENT ); +} +``` + +Now to execute some code when an event is received from an input, you must define the method onAction: + +```js +MyNode.prototype.onAction = function(action, data) +{ + if(action == "play") + { + //do your action... + } + +} +``` + +And the last thing is to trigger events when something in your node happens. You could trigger them from inside the onExecute or from any other interaction: + +```js +MyNode.prototype.onAction = function(action, data) +{ + if( this.button_was_clicked ) + this.triggerSlot(0); //triggers event in slot 0 +} +``` + +There are some nodes already available to handle events, like delaying, counting, etc. + + + + + + + +``` + + + + + + + + + + diff --git a/imgs/node_graph_example.png b/imgs/node_graph_example.png index 313af60c2..1ff51fac9 100644 Binary files a/imgs/node_graph_example.png and b/imgs/node_graph_example.png differ diff --git a/package.json b/package.json index 5dd22a02d..60ebc934b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "litegraph.js", - "version": "0.7.3", - "description": "A graph node editor similar to PD or UDK Blueprints, it works in a HTML5 Canvas and allow to exported graphs to be included in applications.", + "version": "0.7.8", + "description": "A graph node editor similar to PD or UDK Blueprints. It works in an HTML5 Canvas and allows to export graphs to be included in applications.", "main": "build/litegraph.js", "types": "src/litegraph.d.ts", "directories": { @@ -31,11 +31,11 @@ "bugs": { "url": "https://github.com/jagenjo/litegraph.js/issues" }, - "homepage": "https://github.com/kriffe/litegraph.js#readme", + "homepage": "https://github.com/jagenjo/litegraph.js#readme", "devDependencies": { "express": "^4.17.1", "google-closure-compiler": "^20171112.0.0", - "grunt": "^1.0.4", + "grunt": "^1.1.0", "grunt-cli": "^1.2.0", "grunt-closure-tools": "^1.0.0", "grunt-contrib-concat": "^1.0.1", diff --git a/src/litegraph-editor.js b/src/litegraph-editor.js index abf77fcbf..e8ddb80b5 100755 --- a/src/litegraph-editor.js +++ b/src/litegraph-editor.js @@ -3,19 +3,17 @@ function Editor(container_id, options) { options = options || {}; //fill container - var html = - "
"; - html += - "
"; - html += - ""; + var html = "
"; + html += "
"; + html += ""; var root = document.createElement("div"); this.root = root; - root.className = "litegraph-editor"; + root.className = "litegraph litegraph-editor"; root.innerHTML = html; this.tools = root.querySelector(".tools"); + this.content = root.querySelector(".content"); this.footer = root.querySelector(".footer"); var canvas = root.querySelector(".graphcanvas"); @@ -28,6 +26,8 @@ function Editor(container_id, options) { graphcanvas.draw(true); }; + graphcanvas.onDropItem = this.onDropItem.bind(this); + //add stuff //this.addToolsButton("loadsession_button","Load","imgs/icon-load.png", this.onLoadButton.bind(this), ".tools-left" ); //this.addToolsButton("savesession_button","Save","imgs/icon-save.png", this.onSaveButton.bind(this), ".tools-left" ); @@ -104,56 +104,31 @@ Editor.prototype.addLoadCounter = function() { }, 200); }; -Editor.prototype.addToolsButton = function( - id, - name, - icon_url, - callback, - container -) { +Editor.prototype.addToolsButton = function( id, name, icon_url, callback, container ) { if (!container) { container = ".tools"; } - var button = this.createButton(name, icon_url); + var button = this.createButton(name, icon_url, callback); button.id = id; - button.addEventListener("click", callback); - this.root.querySelector(container).appendChild(button); }; -Editor.prototype.createPanel = function(title, options) { - var root = document.createElement("div"); - root.className = "dialog"; - root.innerHTML = - "
" + - title + - "
"; - root.header = root.querySelector(".dialog-header"); - root.content = root.querySelector(".dialog-content"); - root.footer = root.querySelector(".dialog-footer"); - - return root; -}; - -Editor.prototype.createButton = function(name, icon_url) { +Editor.prototype.createButton = function(name, icon_url, callback) { var button = document.createElement("button"); if (icon_url) { button.innerHTML = " "; } + button.classList.add("btn"); button.innerHTML += name; + if(callback) + button.addEventListener("click", callback ); return button; }; Editor.prototype.onLoadButton = function() { - var panel = this.createPanel("Load session"); - var close = this.createButton("Close"); - close.style.float = "right"; - close.addEventListener("click", function() { - panel.parentNode.removeChild(panel); - }); - panel.header.appendChild(close); - panel.content.innerHTML = "test"; + var panel = this.graphcanvas.createPanel("Load session",{closable:true}); + //TO DO this.root.appendChild(panel); }; @@ -192,6 +167,25 @@ Editor.prototype.onLiveButton = function() { : " Edit"; }; +Editor.prototype.onDropItem = function(e) +{ + var that = this; + for(var i = 0; i < e.dataTransfer.files.length; ++i) + { + var file = e.dataTransfer.files[i]; + var ext = LGraphCanvas.getFileExtension(file.name); + var reader = new FileReader(); + if(ext == "json") + { + reader.onload = function(event) { + var data = JSON.parse( event.target.result ); + that.graph.configure(data); + }; + reader.readAsText(file); + } + } +} + Editor.prototype.goFullscreen = function() { if (this.root.requestFullscreen) { this.root.requestFullscreen(Element.ALLOW_KEYBOARD_INPUT); @@ -225,7 +219,7 @@ Editor.prototype.addMiniWindow = function(w, h) { var canvas = miniwindow.querySelector("canvas"); var that = this; - var graphcanvas = new LGraphCanvas(canvas, this.graph); + var graphcanvas = new LGraphCanvas( canvas, this.graph ); graphcanvas.show_info = false; graphcanvas.background_image = "imgs/grid.png"; graphcanvas.scale = 0.25; @@ -263,7 +257,7 @@ Editor.prototype.addMiniWindow = function(w, h) { var close_button = document.createElement("div"); close_button.className = "corner-button"; - close_button.innerHTML = "X"; + close_button.innerHTML = "❌"; close_button.addEventListener("click", function(e) { graphcanvas.setGraph(null); miniwindow.parentNode.removeChild(miniwindow); diff --git a/src/litegraph.d.ts b/src/litegraph.d.ts index 8c2a0e852..4f2a875d4 100644 --- a/src/litegraph.d.ts +++ b/src/litegraph.d.ts @@ -11,11 +11,17 @@ export type widgetTypes = | "text" | "toggle" | "button"; +export type SlotShape = + | typeof LiteGraph.BOX_SHAPE + | typeof LiteGraph.CIRCLE_SHAPE + | typeof LiteGraph.ARROW_SHAPE + | typeof LiteGraph.SQUARE_SHAPE + | number; // For custom shapes /** https://github.com/jagenjo/litegraph.js/tree/master/guides#node-slots */ export interface INodeSlot { name: string; - type: string; + type: string | -1; label?: string; dir?: | typeof LiteGraph.UP @@ -24,6 +30,7 @@ export interface INodeSlot { | typeof LiteGraph.LEFT; color_on?: string; color_off?: string; + shape?: SlotShape; locked?: boolean; nameLocked?: boolean; } @@ -68,11 +75,12 @@ export interface IWidget { * https://github.com/jagenjo/litegraph.js/issues/76 */ mouse?( - ctx: undefined, event: MouseEvent, pos: Vector2, node: LGraphNode - ): void; + ): boolean; + /** Called by `LGraphNode.computeSize` */ + computeSize?(width: number): [number, number]; } export interface IButtonWidget extends IWidget { type: "button"; @@ -173,6 +181,7 @@ export const LiteGraph: { CIRCLE_SHAPE: 3; CARD_SHAPE: 4; ARROW_SHAPE: 5; + SQUARE_SHAPE: 6; //enums INPUT: 1; @@ -228,6 +237,10 @@ export const LiteGraph: { createNode(type: string): T; /** Register a node class so it can be listed when the user wants to create a new one */ registerNodeType(type: string, base: { new (): LGraphNode }): void; + /** removes a node type from the system */ + unregisterNodeType(type: string): void; + /** Removes all previously registered node's types. */ + clearRegisteredTypes(): void; /** * Create a new node type by passing a function, it wraps it with a proper class and generates inputs according to the parameters of the function. * Useful to wrap simple methods that do not require properties, and that only process some input to generate an output. @@ -240,7 +253,7 @@ export const LiteGraph: { wrapFunctionAsNode( name: string, func: (...args: any[]) => any, - param_types?: [], + param_types?: string[], return_type?: string, properties?: object ): void; @@ -431,6 +444,11 @@ export declare class LGraph { * @param node the instance of the node */ add(node: LGraphNode, skip_compute_order?: boolean): void; + /** + * Called when a new node is added + * @param node the instance of the node + */ + onNodeAdded(node: LGraphNode): void; /** Removes a node from the graph */ remove(node: LGraphNode): void; /** Returns a node by its id. */ @@ -593,7 +611,9 @@ export declare class LGraphNode { properties: Record; properties_info: any[]; - flags: object; + flags: Partial<{ + collapsed: boolean + }>; color: string; bgcolor: string; @@ -615,6 +635,17 @@ export declare class LGraphNode { | typeof LiteGraph.NEVER | typeof LiteGraph.ALWAYS; + /** If set to true widgets do not start after the slots */ + widgets_up: boolean; + /** widgets start at y distance from the top of the node */ + widgets_start_y: number; + /** if you render outside the node, it will be clipped */ + clip_area: boolean; + /** if set to false it wont be resizable with the mouse */ + resizable: boolean; + /** slots are distributed horizontally */ + horizontal: boolean; + /** configure a node from an object containing the serialized info */ configure(info: SerializedLGraphNode): void; /** serialize the content */ @@ -707,7 +738,7 @@ export declare class LGraphNode { */ addOutput( name: string, - type: string, + type: string | -1, extra_info?: Partial ): void; /** @@ -715,7 +746,7 @@ export declare class LGraphNode { * @param array of triplets like [[name,type,extra_info],[...]] */ addOutputs( - array: [string, string, Partial | undefined][] + array: [string, string | -1, Partial | undefined][] ): void; /** remove an existing output slot */ removeOutput(slot: number): void; @@ -727,7 +758,7 @@ export declare class LGraphNode { */ addInput( name: string, - type: string, + type: string | -1, extra_info?: Partial ): void; /** @@ -735,7 +766,7 @@ export declare class LGraphNode { * @param array of triplets like [[name,type,extra_info],[...]] */ addInputs( - array: [string, string, Partial | undefined][] + array: [string, string | -1, Partial | undefined][] ): void; /** remove an existing input slot */ removeInput(slot: number): void; @@ -769,7 +800,7 @@ export declare class LGraphNode { type: T["type"], name: string, value: T["value"], - callback?: WidgetCallback, + callback?: WidgetCallback | string, options?: T["options"] ): T; @@ -863,12 +894,12 @@ export declare class LGraphNode { // https://github.com/jagenjo/litegraph.js/blob/master/guides/README.md#custom-node-appearance onDrawBackground?( - canvas: HTMLCanvasElement, - ctx: CanvasRenderingContext2D + ctx: CanvasRenderingContext2D, + canvas: HTMLCanvasElement ): void; onDrawForeground?( - canvas: HTMLCanvasElement, - ctx: CanvasRenderingContext2D + ctx: CanvasRenderingContext2D, + canvas: HTMLCanvasElement ): void; // https://github.com/jagenjo/litegraph.js/blob/master/guides/README.md#custom-node-behaviour @@ -926,10 +957,34 @@ export declare class LGraphNode { onConnectInput?( inputIndex: number, type: INodeOutputSlot["type"], - outputSlot: INodeOutputSlot + outputSlot: INodeOutputSlot, + _this: this, + slotIndex: number ): boolean; + + /** + * Called just before connection (or disconnect - if input is linked). + * A convenient place to switch to another input, or create new one. + * This allow for ability to automatically add slots if needed + * @param inputIndex + * @return selected input slot index, can differ from parameter value + */ + onBeforeConnectInput?( + inputIndex: number + ): number; + + /** a connection changed (new one or removed) (LiteGraph.INPUT or LiteGraph.OUTPUT, slot, true if connected, link_info, input_info or output_info ) */ + onConnectionsChange( + type: number, + slotIndex: number, + isConnected: boolean, + link: LLink, + ioSlot: (INodeOutputSlot | INodeInputSlot) + ): void; + /** Called by `LGraphCanvas.processContextMenu` */ getMenuOptions?(graphCanvas: LGraphCanvas): ContextMenuItem[]; + getSlotMenuOptions?(slot: INodeSlot): ContextMenuItem[]; } export type LGraphNodeConstructor = { @@ -1017,6 +1072,7 @@ export declare class LGraphCanvas { /** Create menu for `Add Node` */ static onMenuAdd: ContextMenuEventListener; static showMenuNodeOptionalInputs: ContextMenuEventListener; + static showMenuNodeOptionalOutputs: ContextMenuEventListener; static onShowMenuNodeProperties: ContextMenuEventListener; static onResizeNode: ContextMenuEventListener; static onMenuNodeCollapse: ContextMenuEventListener; @@ -1089,7 +1145,7 @@ export declare class LGraphCanvas { last_mouse_position: Vector2; /** Timestamp of last mouse click, defaults to 0 */ last_mouseclick: number; - link_render_mode: + links_render_mode: | typeof LiteGraph.STRAIGHT_LINK | typeof LiteGraph.LINEAR_LINK | typeof LiteGraph.SPLINE_LINK; @@ -1111,6 +1167,20 @@ export declare class LGraphCanvas { onDrawOverlay: ((ctx: CanvasRenderingContext2D) => void) | null; /** Called by `LGraphCanvas.processMouseDown` */ onMouse: ((event: MouseEvent) => boolean) | null; + /** Called by `LGraphCanvas.drawFrontCanvas` and `LGraphCanvas.drawLinkTooltip` */ + onDrawLinkTooltip: ((ctx: CanvasRenderingContext2D, link: LLink, _this: this) => void) | null; + /** Called by `LGraphCanvas.selectNodes` */ + onNodeMoved: ((node: LGraphNode) => void) | null; + /** Called by `LGraphCanvas.processNodeSelected` */ + onNodeSelected: ((node: LGraphNode) => void) | null; + /** Called by `LGraphCanvas.deselectNode` */ + onNodeDeselected: ((node: LGraphNode) => void) | null; + /** Called by `LGraphCanvas.processNodeDblClicked` */ + onShowNodePanel: ((node: LGraphNode) => void) | null; + /** Called by `LGraphCanvas.processNodeDblClicked` */ + onNodeDblClicked: ((node: LGraphNode) => void) | null; + /** Called by `LGraphCanvas.selectNodes` */ + onSelectionChange: ((nodes: Record) => void) | null; /** Called by `LGraphCanvas.showSearchBox` */ onSearchBox: | (( @@ -1213,7 +1283,7 @@ export declare class LGraphCanvas { /** selects a given node (or adds it to the current selection) */ selectNode(node: LGraphNode, add?: boolean): void; /** selects several nodes (or adds them to the current selection) */ - selectNodes(nodes: LGraphNode[], add?: boolean): void; + selectNodes(nodes?: LGraphNode[], add?: boolean): void; /** removes a node from the current selection */ deselectNode(node: LGraphNode): void; /** removes all nodes from the current selection */ @@ -1236,11 +1306,13 @@ export declare class LGraphCanvas { /** draws the front canvas (the one containing all the nodes) */ drawFrontCanvas(): void; /** draws some useful stats in the corner of the canvas */ - renderInfo(): void; + renderInfo(ctx: CanvasRenderingContext2D, x: number, y: number): void; /** draws the back canvas (the one containing the background and the connections) */ drawBackCanvas(): void; /** draws the given node inside the canvas */ drawNode(node: LGraphNode, ctx: CanvasRenderingContext2D): void; + /** draws graphic for node's slot */ + drawSlotGraphic(ctx: CanvasRenderingContext2D, pos: number[], shape: SlotShape, horizontal: boolean): void; /** draws the shape of the given node in the canvas */ drawNodeShape( node: LGraphNode, diff --git a/src/litegraph.js b/src/litegraph.js index e744218b2..7a2d70dc6 100755 --- a/src/litegraph.js +++ b/src/litegraph.js @@ -91,6 +91,7 @@ registered_node_types: {}, //nodetypes by string node_types_by_file_extension: {}, //used for dropping files in the canvas Nodes: {}, //node types by classname + Globals: {}, //used to store vars between graphs searchbox_extras: {}, //used to add extra features to the search box @@ -132,8 +133,12 @@ } } - if( !Object.hasOwnProperty( base_class.prototype, "shape") ) + var prev = this.registered_node_types[type]; + if(prev) + console.log("replacing node type: " + type); + else { + if( !Object.hasOwnProperty( base_class.prototype, "shape") ) Object.defineProperty(base_class.prototype, "shape", { set: function(v) { switch (v) { @@ -159,13 +164,28 @@ get: function(v) { return this._shape; }, - enumerable: true + enumerable: true, + configurable: true }); - } - var prev = this.registered_node_types[type]; - if(prev) - console.log("replacing node type: " + type); + //warnings + if (base_class.prototype.onPropertyChange) { + console.warn( + "LiteGraph node class " + + type + + " has onPropertyChange method, it must be called onPropertyChanged with d at the end" + ); + } + + //used to know which nodes create when dragging files to the canvas + if (base_class.supported_extensions) { + for (var i in base_class.supported_extensions) { + var ext = base_class.supported_extensions[i]; + if(ext && ext.constructor === String) + this.node_types_by_file_extension[ ext.toLowerCase() ] = base_class; + } + } + } this.registered_node_types[type] = base_class; if (base_class.constructor.name) { @@ -197,6 +217,20 @@ } }, + /** + * removes a node type from the system + * @method unregisterNodeType + * @param {String|Object} type name of the node or the node constructor itself + */ + unregisterNodeType: function(type) { + var base_class = type.constructor === String ? this.registered_node_types[type] : type; + if(!base_class) + throw("node type not found: " + type ); + delete this.registered_node_types[base_class.type]; + if(base_class.constructor.name) + delete this.Nodes[base_class.constructor.name]; + }, + /** * Create a new nodetype by passing a function, it wraps it with a proper class and generates inputs according to the parameters of the function. * Useful to wrap simple methods that do not require properties, and that only process some input to generate an output. @@ -248,6 +282,16 @@ this.registerNodeType(name, classobj); }, + /** + * Removes all previously registered node's types + */ + clearRegisteredTypes: function() { + this.registered_node_types = {}; + this.node_types_by_file_extension = {}; + this.Nodes = {}; + this.searchbox_extras = {}; + }, + /** * Adds this method to all nodetypes, existing and to be created * (You can add it to LGraphNode.prototype but then existing node types wont have it) @@ -317,6 +361,7 @@ } if (!node.size) { node.size = node.computeSize(); + //call onresize? } if (!node.pos) { node.pos = LiteGraph.DEFAULT_POSITION.concat(); @@ -356,7 +401,7 @@ var r = []; for (var i in this.registered_node_types) { var type = this.registered_node_types[i]; - if (filter && type.filter && type.filter != filter) { + if (type.filter != filter) { continue; } @@ -375,16 +420,16 @@ /** * Returns a list with all the node type categories * @method getNodeTypesCategories + * @param {String} filter only nodes with ctor.filter equal can be shown * @return {Array} array with all the names of the categories */ - getNodeTypesCategories: function( filter ) { var categories = { "": 1 }; for (var i in this.registered_node_types) { var type = this.registered_node_types[i]; if ( type.category && !type.skip_list ) { - if(filter && type.filter != filter) + if(type.filter != filter) continue; categories[type.category] = 1; } @@ -457,6 +502,13 @@ return target; }, + /** + * Returns if the types of two slots are compatible (taking into account wildcards, etc) + * @method isValidConnection + * @param {String} type_a + * @param {String} type_b + * @return {Boolean} true if they can be connected + */ isValidConnection: function(type_a, type_b) { if ( !type_a || //generic output @@ -492,13 +544,85 @@ return false; }, + /** + * Register a string in the search box so when the user types it it will recommend this node + * @method registerSearchboxExtra + * @param {String} node_type the node recommended + * @param {String} description text to show next to it + * @param {Object} data it could contain info of how the node should be configured + * @return {Boolean} true if they can be connected + */ registerSearchboxExtra: function(node_type, description, data) { this.searchbox_extras[description.toLowerCase()] = { type: node_type, desc: description, data: data }; - } + }, + + /** + * Wrapper to load files (from url using fetch or from file using FileReader) + * @method fetchFile + * @param {String|File|Blob} url the url of the file (or the file itself) + * @param {String} type an string to know how to fetch it: "text","arraybuffer","json","blob" + * @param {Function} on_complete callback(data) + * @param {Function} on_error in case of an error + * @return {FileReader|Promise} returns the object used to + */ + fetchFile: function( url, type, on_complete, on_error ) { + var that = this; + if(!url) + return null; + + type = type || "text"; + if( url.constructor === String ) + { + if (url.substr(0, 4) == "http" && LiteGraph.proxy) { + url = LiteGraph.proxy + url.substr(url.indexOf(":") + 3); + } + return fetch(url) + .then(function(response) { + if(!response.ok) + throw new Error("File not found"); //it will be catch below + if(type == "arraybuffer") + return response.arrayBuffer(); + else if(type == "text" || type == "string") + return response.text(); + else if(type == "json") + return response.json(); + else if(type == "blob") + return response.blob(); + }) + .then(function(data) { + if(on_complete) + on_complete(data); + }) + .catch(function(error) { + console.error("error fetching file:",url); + if(on_error) + on_error(error); + }); + } + else if( url.constructor === File || url.constructor === Blob) + { + var reader = new FileReader(); + reader.onload = function(e) + { + var v = e.target.result; + if( type == "json" ) + v = JSON.parse(v); + if(on_complete) + on_complete(v); + } + if(type == "arraybuffer") + return reader.readAsArrayBuffer(url); + else if(type == "text" || type == "json") + return reader.readAsText(url); + else if(type == "blob") + return reader.readAsBinaryString(url); + } + return null; + } }); //timer that works everywhere @@ -523,6 +647,10 @@ /** * LGraph is the class that contain a full graph. We instantiate one and add nodes to it, and then we can run the execution loop. + * supported callbacks: + + onNodeAdded: when a new node is added to the graph + + onNodeRemoved: when a node inside this graph is removed + + onNodeConnectionChange: some connection has changed in the graph (connected or disconnected) * * @class LGraph * @constructor @@ -581,8 +709,8 @@ //nodes this._nodes = []; this._nodes_by_id = {}; - this._nodes_in_order = []; //nodes that are executable sorted in execution order - this._nodes_executable = null; //nodes that contain onExecute + this._nodes_in_order = []; //nodes sorted in execution order + this._nodes_executable = null; //nodes that contain onExecute sorted in execution order //other scene stuff this._groups = []; @@ -596,6 +724,7 @@ //custom data this.config = {}; this.vars = {}; + this.extra = {}; //to store custom data //timing this.globaltime = 0; @@ -633,6 +762,7 @@ } graphcanvas.graph = this; + if (!this.list_of_graphcanvas) { this.list_of_graphcanvas = []; } @@ -681,24 +811,29 @@ interval = interval || 0; var that = this; - if ( - interval == 0 && - typeof window != "undefined" && - window.requestAnimationFrame - ) { + //execute once per frame + if ( interval == 0 && typeof window != "undefined" && window.requestAnimationFrame ) { function on_frame() { if (that.execution_timer_id != -1) { return; } window.requestAnimationFrame(on_frame); - that.runStep(1, !this.catch_errors); + if(that.onBeforeStep) + that.onBeforeStep(); + that.runStep(1, !that.catch_errors); + if(that.onAfterStep) + that.onAfterStep(); } this.execution_timer_id = -1; on_frame(); - } else { + } else { //execute every 'interval' ms this.execution_timer_id = setInterval(function() { //execute - that.runStep(1, !this.catch_errors); + if(that.onBeforeStep) + that.onBeforeStep(); + that.runStep(1, !that.catch_errors); + if(that.onAfterStep) + that.onAfterStep(); }, interval); } }; @@ -1281,6 +1416,9 @@ this.onNodeRemoved(node); } + //close panels + this.sendActionToCanvas("checkPanels"); + this.setDirtyCanvas(true, true); this.change(); @@ -1469,8 +1607,10 @@ return; } + this.beforeChange(); this.inputs[name] = { name: name, type: type, value: value }; this._version++; + this.afterChange(); if (this.onInputAdded) { this.onInputAdded(name, type); @@ -1731,6 +1871,22 @@ } }; + //used for undo, called before any change is made to the graph + LGraph.prototype.beforeChange = function(info) { + if (this.onBeforeChange) { + this.onBeforeChange(this,info); + } + this.sendActionToCanvas("onBeforeChange", this); + }; + + //used to resend actions, called after any change is made to the graph + LGraph.prototype.afterChange = function(info) { + if (this.onAfterChange) { + this.onAfterChange(this,info); + } + this.sendActionToCanvas("onAfterChange", this); + }; + LGraph.prototype.connectionChange = function(node, link_info) { this.updateExecutionOrder(); if (this.onConnectionChange) { @@ -1851,9 +2007,13 @@ links: links, groups: groups_info, config: this.config, + extra: this.extra, version: LiteGraph.VERSION }; + if(this.onSerialize) + this.onSerialize(data); + return data; }; @@ -1893,7 +2053,7 @@ //copy all stored fields for (var i in data) { - if(i == "nodes" || i == "groups") + if(i == "nodes" || i == "groups" ) //links must be accepted continue; this[i] = data[i]; } @@ -1946,6 +2106,12 @@ } this.updateExecutionOrder(); + + this.extra = data.extra || {}; + + if(this.onConfigure) + this.onConfigure(data); + this._version++; this.setDirtyCanvas(true, true); return error; @@ -2203,6 +2369,8 @@ for (var i = 0; i < this.widgets.length; ++i) { var w = this.widgets[i]; + if(!w) + continue; if(w.options && w.options.property && this.properties[ w.options.property ]) w.value = JSON.parse( JSON.stringify( this.properties[ w.options.property ] ) ); } @@ -2265,7 +2433,10 @@ if (this.widgets && this.serialize_widgets) { o.widgets_values = []; for (var i = 0; i < this.widgets.length; ++i) { - o.widgets_values[i] = this.widgets[i].value; + if(this.widgets[i]) + o.widgets_values[i] = this.widgets[i].value; + else + o.widgets_values[i] = null; } } @@ -2366,6 +2537,18 @@ if( this.onPropertyChanged(name, value, prev_value) === false ) //abort change this.properties[name] = prev_value; } + if(this.widgets) //widgets could be linked to properties + for(var i = 0; i < this.widgets.length; ++i) + { + var w = this.widgets[i]; + if(!w) + continue; + if(w.options.property == name) + { + w.value = value; + break; + } + } }; // Execution ************************* @@ -2556,6 +2739,23 @@ return null; }; + /** + * Returns the link info in the connection of an input slot + * @method getInputLink + * @param {number} slot + * @return {LLink} object or null + */ + LGraphNode.prototype.getInputLink = function(slot) { + if (!this.inputs) { + return null; + } + if (slot < this.inputs.length) { + var slot_info = this.inputs[slot]; + return this.graph.links[ slot_info.link ]; + } + return null; + }; + /** * returns the node connected in the input slot * @method getInputNode @@ -2822,6 +3022,18 @@ } }; + /** + * changes node size and triggers callback + * @method setSize + * @param {vec2} size + */ + LGraphNode.prototype.setSize = function(size) + { + this.size = size; + if(this.onResize) + this.onResize(this.size); + } + /** * add a new property to this node * @method addProperty @@ -2877,7 +3089,7 @@ if (this.onOutputAdded) { this.onOutputAdded(o); } - this.size = this.computeSize(); + this.setSize( this.computeSize() ); this.setDirtyCanvas(true, true); return o; }; @@ -2906,7 +3118,7 @@ } } - this.size = this.computeSize(); + this.setSize( this.computeSize() ); this.setDirtyCanvas(true, true); }; @@ -2932,7 +3144,7 @@ } } - this.size = this.computeSize(); + this.setSize( this.computeSize() ); if (this.onOutputRemoved) { this.onOutputRemoved(slot); } @@ -2958,11 +3170,14 @@ if (!this.inputs) { this.inputs = []; } + this.inputs.push(o); - this.size = this.computeSize(); + this.setSize( this.computeSize() ); + if (this.onInputAdded) { this.onInputAdded(o); } + this.setDirtyCanvas(true, true); return o; }; @@ -2991,7 +3206,7 @@ } } - this.size = this.computeSize(); + this.setSize( this.computeSize() ); this.setDirtyCanvas(true, true); }; @@ -3002,7 +3217,7 @@ */ LGraphNode.prototype.removeInput = function(slot) { this.disconnectInput(slot); - this.inputs.splice(slot, 1); + var slot_info = this.inputs.splice(slot, 1); for (var i = slot; i < this.inputs.length; ++i) { if (!this.inputs[i]) { continue; @@ -3013,9 +3228,9 @@ } link.target_slot -= 1; } - this.size = this.computeSize(); + this.setSize( this.computeSize() ); if (this.onInputRemoved) { - this.onInputRemoved(slot); + this.onInputRemoved(slot, slot_info[0] ); } this.setDirtyCanvas(true, true); }; @@ -3041,12 +3256,12 @@ }; /** - * computes the size of a node according to its inputs and output slots + * computes the minimum size of a node according to its inputs and output slots * @method computeSize * @param {number} minHeight * @return {number} the total size */ - LGraphNode.prototype.computeSize = function(minHeight, out) { + LGraphNode.prototype.computeSize = function(out) { if (this.constructor.size) { return this.constructor.size.concat(); } @@ -3058,20 +3273,6 @@ var size = out || new Float32Array([0, 0]); rows = Math.max(rows, 1); var font_size = LiteGraph.NODE_TEXT_SIZE; //although it should be graphcanvas.inner_text_font size - size[1] = (this.constructor.slot_start_y || 0) + rows * LiteGraph.NODE_SLOT_HEIGHT; - - var widgets_height = 0; - if (this.widgets && this.widgets.length) { - widgets_height = this.widgets.length * (LiteGraph.NODE_WIDGET_HEIGHT + 4) + 8; - } - - //compute height using widgets height - if( this.widgets_up ) - size[1] = Math.max( size[1], widgets_height ); - else if( this.widgets_start_y != null ) - size[1] = Math.max( size[1], widgets_height + this.widgets_start_y ); - else - size[1] += widgets_height; var font_size = font_size; var title_width = compute_text_size(this.title); @@ -3106,10 +3307,27 @@ size[0] = Math.max(size[0], LiteGraph.NODE_WIDTH * 1.5); } - if (this.onResize) { - this.onResize(size); + size[1] = (this.constructor.slot_start_y || 0) + rows * LiteGraph.NODE_SLOT_HEIGHT; + + var widgets_height = 0; + if (this.widgets && this.widgets.length) { + for (var i = 0, l = this.widgets.length; i < l; ++i) { + if (this.widgets[i].computeSize) + widgets_height += this.widgets[i].computeSize(size[0])[1] + 4; + else + widgets_height += LiteGraph.NODE_WIDGET_HEIGHT + 4; + } + widgets_height += 8; } + //compute height using widgets height + if( this.widgets_up ) + size[1] = Math.max( size[1], widgets_height ); + else if( this.widgets_start_y != null ) + size[1] = Math.max( size[1], widgets_height + this.widgets_start_y ); + else + size[1] += widgets_height; + function compute_text_size(text) { if (!text) { return 0; @@ -3130,13 +3348,56 @@ }; /** - * Allows to pass + * returns all the info available about a property of this node. + * + * @method getPropertyInfo + * @param {String} property name of the property + * @return {Object} the object with all the available info + */ + LGraphNode.prototype.getPropertyInfo = function( property ) + { + var info = null; + + //there are several ways to define info about a property + //legacy mode + if (this.properties_info) { + for (var i = 0; i < this.properties_info.length; ++i) { + if (this.properties_info[i].name == property) { + info = this.properties_info[i]; + break; + } + } + } + //litescene mode using the constructor + if(this.constructor["@" + property]) + info = this.constructor["@" + property]; + + if(this.constructor.widgets_info && this.constructor.widgets_info[property]) + info = this.constructor.widgets_info[property]; + + //litescene mode using the constructor + if (!info && this.onGetPropertyInfo) { + info = this.onGetPropertyInfo(property); + } + + if (!info) + info = {}; + if(!info.type) + info.type = typeof this.properties[property]; + if(info.widget == "combo") + info.type = "enum"; + + return info; + } + + /** + * Defines a widget inside the node, it will be rendered on top of the node, you can control lots of properties * * @method addWidget * @param {String} type the widget type (could be "number","string","combo" * @param {String} name the text to show on the widget * @param {String} value the default value - * @param {Function} callback function to call when it changes (optionally, it can be the name of the property to modify) + * @param {Function|String} callback function to call when it changes (optionally, it can be the name of the property to modify) * @param {Object} options the object that contains special properties of this widget * @return {Object} the created widget object */ @@ -3188,7 +3449,7 @@ throw "LiteGraph addWidget('combo',...) requires to pass values in options: { values:['red','blue'] }"; } this.widgets.push(w); - this.size = this.computeSize(); + this.setSize( this.computeSize() ); return w; }; @@ -3423,9 +3684,13 @@ return null; } + var changed = false; + //if there is something already plugged there, disconnect if (target_node.inputs[target_slot].link != null) { + this.graph.beforeChange(); target_node.disconnectInput(target_slot); + changed = true; } //why here?? @@ -3436,10 +3701,7 @@ //allows nodes to block connection if (target_node.onConnectInput) { - if ( - target_node.onConnectInput(target_slot, output.type, output) === - false - ) { + if ( target_node.onConnectInput(target_slot, output.type, output, this, slot) === false ) { return null; } } @@ -3447,67 +3709,79 @@ var input = target_node.inputs[target_slot]; var link_info = null; - if (LiteGraph.isValidConnection(output.type, input.type)) { - link_info = new LLink( - ++this.graph.last_link_id, - input.type, - this.id, - slot, - target_node.id, - target_slot - ); + //this slots cannot be connected (different types) + if (!LiteGraph.isValidConnection(output.type, input.type)) + { + this.setDirtyCanvas(false, true); + if(changed) + this.graph.connectionChange(this, link_info); + return null; + } - //add to graph links list - this.graph.links[link_info.id] = link_info; + if(!changed) + this.graph.beforeChange(); - //connect in output - if (output.links == null) { - output.links = []; - } - output.links.push(link_info.id); - //connect in input - target_node.inputs[target_slot].link = link_info.id; - if (this.graph) { - this.graph._version++; - } - if (this.onConnectionsChange) { - this.onConnectionsChange( - LiteGraph.OUTPUT, - slot, - true, - link_info, - output - ); - } //link_info has been created now, so its updated - if (target_node.onConnectionsChange) { - target_node.onConnectionsChange( - LiteGraph.INPUT, - target_slot, - true, - link_info, - input - ); - } - if (this.graph && this.graph.onNodeConnectionChange) { - this.graph.onNodeConnectionChange( - LiteGraph.INPUT, - target_node, - target_slot, - this, - slot - ); - this.graph.onNodeConnectionChange( - LiteGraph.OUTPUT, - this, - slot, - target_node, - target_slot - ); - } - } + //create link class + link_info = new LLink( + ++this.graph.last_link_id, + input.type, + this.id, + slot, + target_node.id, + target_slot + ); + + //add to graph links list + this.graph.links[link_info.id] = link_info; + + //connect in output + if (output.links == null) { + output.links = []; + } + output.links.push(link_info.id); + //connect in input + target_node.inputs[target_slot].link = link_info.id; + if (this.graph) { + this.graph._version++; + } + if (this.onConnectionsChange) { + this.onConnectionsChange( + LiteGraph.OUTPUT, + slot, + true, + link_info, + output + ); + } //link_info has been created now, so its updated + if (target_node.onConnectionsChange) { + target_node.onConnectionsChange( + LiteGraph.INPUT, + target_slot, + true, + link_info, + input + ); + } + if (this.graph && this.graph.onNodeConnectionChange) { + this.graph.onNodeConnectionChange( + LiteGraph.INPUT, + target_node, + target_slot, + this, + slot + ); + this.graph.onNodeConnectionChange( + LiteGraph.OUTPUT, + this, + slot, + target_node, + target_slot + ); + } this.setDirtyCanvas(false, true); - this.graph.connectionChange(this, link_info); + this.graph.afterChange(); + this.graph.connectionChange(this, link_info); return link_info; }; @@ -3868,12 +4142,14 @@ if (!this.console) { this.console = []; } + this.console.push(msg); if (this.console.length > LGraphNode.MAX_CONSOLE) { this.console.shift(); } - this.graph.onNodeTrace(this, msg); + if(this.graph.onNodeTrace) + this.graph.onNodeTrace(this, msg); }; /* Forces to redraw or the main canvas (LGraphNode) or the bg canvas (links) */ @@ -4340,8 +4616,7 @@ LGraphNode.prototype.executeAction = function(action) //if(graph === undefined) // throw ("No graph assigned"); - this.background_image = - "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQBJREFUeNrs1rEKwjAUhlETUkj3vP9rdmr1Ysammk2w5wdxuLgcMHyptfawuZX4pJSWZTnfnu/lnIe/jNNxHHGNn//HNbbv+4dr6V+11uF527arU7+u63qfa/bnmh8sWLBgwYJlqRf8MEptXPBXJXa37BSl3ixYsGDBMliwFLyCV/DeLIMFCxYsWLBMwSt4Be/NggXLYMGCBUvBK3iNruC9WbBgwYJlsGApeAWv4L1ZBgsWLFiwYJmCV/AK3psFC5bBggULloJX8BpdwXuzYMGCBctgwVLwCl7Be7MMFixYsGDBsu8FH1FaSmExVfAxBa/gvVmwYMGCZbBg/W4vAQYA5tRF9QYlv/QAAAAASUVORK5CYII="; + this.background_image = LGraphCanvas.DEFAULT_BACKGROUND_IMAGE; if (canvas && canvas.constructor === String) { canvas = document.querySelector(canvas); @@ -4383,6 +4658,7 @@ LGraphNode.prototype.executeAction = function(action) this.filter = null; //allows to filter to only accept some type of nodes in a graph + this.set_canvas_dirty_on_mouse_event = true; //forces to redraw the canvas if the mouse does anything this.always_render_background = false; this.render_shadows = true; this.render_canvas_border = true; @@ -4397,7 +4673,9 @@ LGraphNode.prototype.executeAction = function(action) this.links_render_mode = LiteGraph.SPLINE_LINK; - this.canvas_mouse = [0, 0]; //mouse in canvas graph coordinates, where 0,0 is the top-left corner of the blue rectangle + this.mouse = [0, 0]; //mouse in canvas coordinates, where 0,0 is the top-left corner of the blue rectangle + this.graph_mouse = [0, 0]; //mouse in graph coordinates, where 0,0 is the top-left corner of the blue rectangle + this.canvas_mouse = this.graph_mouse; //LEGACY: REMOVE THIS, USE GRAPH_MOUSE INSTEAD //to personalize the search box this.onSearchBox = null; @@ -4411,6 +4689,9 @@ LGraphNode.prototype.executeAction = function(action) this.onDrawLinkTooltip = null; //called when rendering a tooltip this.onNodeMoved = null; //called after moving a node this.onSelectionChange = null; //called if the selection changes + this.onConnectingChange = null; //called before any link changes + this.onBeforeChange = null; //called before modifying the graph + this.onAfterChange = null; //called after modifying the graph this.connections_width = 3; this.round_radius = 8; @@ -4439,6 +4720,8 @@ LGraphNode.prototype.executeAction = function(action) global.LGraphCanvas = LiteGraph.LGraphCanvas = LGraphCanvas; + LGraphCanvas.DEFAULT_BACKGROUND_IMAGE = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQBJREFUeNrs1rEKwjAUhlETUkj3vP9rdmr1Ysammk2w5wdxuLgcMHyptfawuZX4pJSWZTnfnu/lnIe/jNNxHHGNn//HNbbv+4dr6V+11uF527arU7+u63qfa/bnmh8sWLBgwYJlqRf8MEptXPBXJXa37BSl3ixYsGDBMliwFLyCV/DeLIMFCxYsWLBMwSt4Be/NggXLYMGCBUvBK3iNruC9WbBgwYJlsGApeAWv4L1ZBgsWLFiwYJmCV/AK3psFC5bBggULloJX8BpdwXuzYMGCBctgwVLwCl7Be7MMFixYsGDBsu8FH1FaSmExVfAxBa/gvVmwYMGCZbBg/W4vAQYA5tRF9QYlv/QAAAAASUVORK5CYII="; + LGraphCanvas.link_type_colors = { "-1": LiteGraph.EVENT_LINK_COLOR, number: "#AAA", @@ -4472,6 +4755,8 @@ LGraphNode.prototype.executeAction = function(action) this.connecting_node = null; this.highlighted_links = {}; + this.dragging_canvas = false; + this.dirty_canvas = true; this.dirty_bgcanvas = true; this.dirty_area = null; @@ -4508,17 +4793,28 @@ LGraphNode.prototype.executeAction = function(action) return; } - /* - if(this.graph) - this.graph.canvas = null; //remove old graph link to the canvas - this.graph = graph; - if(this.graph) - this.graph.canvas = this; - */ graph.attachCanvas(this); + + //remove the graph stack in case a subgraph was open + if (this._graph_stack) + this._graph_stack = null; + this.setDirty(true, true); }; + /** + * returns the top level graph (in case there are subgraphs open on the canvas) + * + * @method getTopGraph + * @return {LGraph} graph + */ + LGraphCanvas.prototype.getTopGraph = function() + { + if(this._graph_stack.length) + return this._graph_stack[0]; + return this.graph; + } + /** * opens a graph contained inside a node in the current graph * @@ -4544,6 +4840,7 @@ LGraphNode.prototype.executeAction = function(action) } graph.attachCanvas(this); + this.checkPanels(); this.setDirty(true, true); }; @@ -4675,6 +4972,7 @@ LGraphNode.prototype.executeAction = function(action) } var canvas = this.canvas; + var ref_window = this.getCanvasWindow(); var document = ref_window.document; //hack used when moving canvas between windows @@ -4871,8 +5169,19 @@ LGraphNode.prototype.executeAction = function(action) /* LiteGraphCanvas input */ + //used to block future mouse events (because of im gui) + LGraphCanvas.prototype.blockClick = function() + { + this.block_click = true; + this.last_mouseclick = 0; + } + LGraphCanvas.prototype.processMouseDown = function(e) { - if (!this.graph) { + + if( this.set_canvas_dirty_on_mouse_event ) + this.dirty_canvas = true; + + if (!this.graph) { return; } @@ -4906,22 +5215,27 @@ LGraphNode.prototype.executeAction = function(action) var skip_action = false; var now = LiteGraph.getTime(); var is_double_click = now - this.last_mouseclick < 300; + this.mouse[0] = e.localX; + this.mouse[1] = e.localY; + this.graph_mouse[0] = e.canvasX; + this.graph_mouse[1] = e.canvasY; + this.last_click_position = [this.mouse[0],this.mouse[1]]; - this.canvas_mouse[0] = e.canvasX; - this.canvas_mouse[1] = e.canvasY; this.canvas.focus(); LiteGraph.closeAllContextMenus(ref_window); - if (this.onMouse) { - if (this.onMouse(e) == true) { + if (this.onMouse) + { + if (this.onMouse(e) == true) return; - } } - if (e.which == 1) { - //left button mouse - if (e.ctrlKey) { + //left button mouse + if (e.which == 1) + { + if (e.ctrlKey) + { this.dragging_rectangle = new Float32Array(4); this.dragging_rectangle[0] = e.canvasX; this.dragging_rectangle[1] = e.canvasY; @@ -4958,6 +5272,7 @@ LGraphNode.prototype.executeAction = function(action) 10 ) ) { + this.graph.beforeChange(); this.resizing_node = node; this.canvas.style.cursor = "se-resize"; skip_action = true; @@ -5065,21 +5380,13 @@ LGraphNode.prototype.executeAction = function(action) } //not resizing } - //Search for corner for collapsing - /* - if( !skip_action && isInsideRectangle( e.canvasX, e.canvasY, node.pos[0], node.pos[1] - LiteGraph.NODE_TITLE_HEIGHT, LiteGraph.NODE_TITLE_HEIGHT, LiteGraph.NODE_TITLE_HEIGHT )) - { - node.collapse(); - skip_action = true; - } - */ - //it wasn't clicked on the links boxes if (!skip_action) { var block_drag_node = false; + var pos = [e.canvasX - node.pos[0], e.canvasY - node.pos[1]]; //widgets - var widget = this.processNodeWidgets( node, this.canvas_mouse, e ); + var widget = this.processNodeWidgets( node, this.graph_mouse, e ); if (widget) { block_drag_node = true; this.node_widget = [node, widget]; @@ -5089,22 +5396,36 @@ LGraphNode.prototype.executeAction = function(action) if (is_double_click && this.selected_nodes[node.id]) { //double click node if (node.onDblClick) { - node.onDblClick( e, [ e.canvasX - node.pos[0], e.canvasY - node.pos[1] ], this ); + node.onDblClick( e, pos, this ); } this.processNodeDblClicked(node); block_drag_node = true; } //if do not capture mouse - if ( node.onMouseDown && node.onMouseDown( e, [e.canvasX - node.pos[0], e.canvasY - node.pos[1]], this ) ) { - block_drag_node = true; - } else if (this.live_mode) { - clicking_canvas_bg = true; + if ( node.onMouseDown && node.onMouseDown( e, pos, this ) ) { block_drag_node = true; + } else { + //open subgraph button + if(node.subgraph && !node.skip_subgraph_button) + { + if ( !node.flags.collapsed && pos[0] > node.size[0] - LiteGraph.NODE_TITLE_HEIGHT && pos[1] < 0 ) { + var that = this; + setTimeout(function() { + that.openSubgraph(node.subgraph); + }, 10); + } + } + + if (this.live_mode) { + clicking_canvas_bg = true; + block_drag_node = true; + } } if (!block_drag_node) { if (this.allow_dragnodes) { + this.graph.beforeChange(); this.node_dragged = node; } if (!this.selected_nodes[node.id]) { @@ -5213,6 +5534,9 @@ LGraphNode.prototype.executeAction = function(action) this.resize(); } + if( this.set_canvas_dirty_on_mouse_event ) + this.dirty_canvas = true; + if (!this.graph) { return; } @@ -5220,30 +5544,42 @@ LGraphNode.prototype.executeAction = function(action) LGraphCanvas.active_canvas = this; this.adjustMouseEvent(e); var mouse = [e.localX, e.localY]; + this.mouse[0] = mouse[0]; + this.mouse[1] = mouse[1]; var delta = [ mouse[0] - this.last_mouse[0], mouse[1] - this.last_mouse[1] ]; this.last_mouse = mouse; - this.canvas_mouse[0] = e.canvasX; - this.canvas_mouse[1] = e.canvasY; + this.graph_mouse[0] = e.canvasX; + this.graph_mouse[1] = e.canvasY; + + if(this.block_click) + { + e.preventDefault(); + return false; + } + e.dragging = this.last_mouse_dragging; if (this.node_widget) { this.processNodeWidgets( this.node_widget[0], - this.canvas_mouse, + this.graph_mouse, e, this.node_widget[1] ); this.dirty_canvas = true; } - if (this.dragging_rectangle) { + if (this.dragging_rectangle) + { this.dragging_rectangle[2] = e.canvasX - this.dragging_rectangle[0]; this.dragging_rectangle[3] = e.canvasY - this.dragging_rectangle[1]; this.dirty_canvas = true; - } else if (this.selected_group && !this.read_only) { + } + else if (this.selected_group && !this.read_only) + { //moving/resizing a group if (this.selected_group_resizing) { this.selected_group.size = [ @@ -5270,18 +5606,11 @@ LGraphNode.prototype.executeAction = function(action) } //get node over - var node = this.graph.getNodeOnPos( - e.canvasX, - e.canvasY, - this.visible_nodes - ); + var node = this.graph.getNodeOnPos(e.canvasX,e.canvasY,this.visible_nodes); //remove mouseover flag for (var i = 0, l = this.graph._nodes.length; i < l; ++i) { - if ( - this.graph._nodes[i].mouseOver && - node != this.graph._nodes[i] - ) { + if (this.graph._nodes[i].mouseOver && node != this.graph._nodes[i] ) { //mouse leave this.graph._nodes[i].mouseOver = false; if (this.node_over && this.node_over.onMouseLeave) { @@ -5294,6 +5623,10 @@ LGraphNode.prototype.executeAction = function(action) //mouse over a node if (node) { + + if(node.redraw_on_mouse) + this.dirty_canvas = true; + //this.canvas.style.cursor = "move"; if (!node.mouseOver) { //mouse enter @@ -5308,11 +5641,7 @@ LGraphNode.prototype.executeAction = function(action) //in case the node wants to do something if (node.onMouseMove) { - node.onMouseMove( - e, - [e.canvasX - node.pos[0], e.canvasY - node.pos[1]], - this - ); + node.onMouseMove( e, [e.canvasX - node.pos[0], e.canvasY - node.pos[1]], this ); } //if dragging a link @@ -5324,20 +5653,10 @@ LGraphNode.prototype.executeAction = function(action) //mouse on top of the corner box, don't know what to do } else { //check if I have a slot below de mouse - var slot = this.isOverNodeInput( - node, - e.canvasX, - e.canvasY, - pos - ); + var slot = this.isOverNodeInput( node, e.canvasX, e.canvasY, pos ); if (slot != -1 && node.inputs[slot]) { var slot_type = node.inputs[slot].type; - if ( - LiteGraph.isValidConnection( - this.connecting_output.type, - slot_type - ) - ) { + if ( LiteGraph.isValidConnection( this.connecting_output.type, slot_type ) ) { this._highlight_input = pos; } } else { @@ -5363,7 +5682,7 @@ LGraphNode.prototype.executeAction = function(action) this.canvas.style.cursor = "crosshair"; } } - } else { //outside + } else { //not over a node //search for link connector var over_link = null; @@ -5391,13 +5710,16 @@ LGraphNode.prototype.executeAction = function(action) if (this.canvas) { this.canvas.style.cursor = ""; } - } + } //end + //send event to node if capturing input (used with widgets that allow drag outside of the area of the node) if ( this.node_capturing_input && this.node_capturing_input != node && this.node_capturing_input.onMouseMove ) { this.node_capturing_input.onMouseMove(e,[e.canvasX - this.node_capturing_input.pos[0],e.canvasY - this.node_capturing_input.pos[1]], this); } + //node being dragged if (this.node_dragged && !this.live_mode) { + //console.log("draggin!",this.selected_nodes); for (var i in this.selected_nodes) { var n = this.selected_nodes[i]; n.pos[0] += delta[0] / this.ds.scale; @@ -5410,23 +5732,11 @@ LGraphNode.prototype.executeAction = function(action) if (this.resizing_node && !this.live_mode) { //convert mouse to node space - this.resizing_node.size[0] = e.canvasX - this.resizing_node.pos[0]; - this.resizing_node.size[1] = e.canvasY - this.resizing_node.pos[1]; - - //constraint size - var max_slots = Math.max( - this.resizing_node.inputs ? this.resizing_node.inputs.length : 0, - this.resizing_node.outputs ? this.resizing_node.outputs.length : 0 - ); - var min_height = - max_slots * LiteGraph.NODE_SLOT_HEIGHT + - (this.resizing_node.widgets ? this.resizing_node.widgets.length : 0) * (LiteGraph.NODE_WIDGET_HEIGHT + 4) + 4; - if (this.resizing_node.size[1] < min_height) { - this.resizing_node.size[1] = min_height; - } - if (this.resizing_node.size[0] < LiteGraph.NODE_MIN_WIDTH) { - this.resizing_node.size[0] = LiteGraph.NODE_MIN_WIDTH; - } + var desired_size = [ e.canvasX - this.resizing_node.pos[0], e.canvasY - this.resizing_node.pos[1] ]; + var min_size = this.resizing_node.computeSize(); + desired_size[0] = Math.max( min_size[0], desired_size[0] ); + desired_size[1] = Math.max( min_size[1], desired_size[1] ); + this.resizing_node.setSize( desired_size ); this.canvas.style.cursor = "se-resize"; this.dirty_canvas = true; @@ -5443,33 +5753,41 @@ LGraphNode.prototype.executeAction = function(action) * @method processMouseUp **/ LGraphCanvas.prototype.processMouseUp = function(e) { - if (!this.graph) { + + if( this.set_canvas_dirty_on_mouse_event ) + this.dirty_canvas = true; + + if (!this.graph) return; - } var window = this.getCanvasWindow(); var document = window.document; LGraphCanvas.active_canvas = this; //restore the mousemove event back to the canvas - document.removeEventListener( - "mousemove", - this._mousemove_callback, - true - ); - this.canvas.addEventListener( - "mousemove", - this._mousemove_callback, - true - ); + document.removeEventListener("mousemove",this._mousemove_callback,true); + this.canvas.addEventListener("mousemove",this._mousemove_callback,true); document.removeEventListener("mouseup", this._mouseup_callback, true); this.adjustMouseEvent(e); var now = LiteGraph.getTime(); e.click_time = now - this.last_mouseclick; this.last_mouse_dragging = false; + this.last_click_position = null; + + if(this.block_click) + { + console.log("foo"); + this.block_click = false; //used to avoid sending twice a click in a immediate button + } if (e.which == 1) { + + if( this.node_widget ) + { + this.processNodeWidgets( this.node_widget[0], this.graph_mouse, e ); + } + //left button this.node_widget = null; @@ -5607,6 +5925,7 @@ LGraphNode.prototype.executeAction = function(action) else if (this.resizing_node) { this.dirty_canvas = true; this.dirty_bgcanvas = true; + this.graph.afterChange(this.resizing_node); this.resizing_node = null; } else if (this.node_dragged) { //node being dragged? @@ -5628,6 +5947,7 @@ LGraphNode.prototype.executeAction = function(action) } if( this.onNodeMoved ) this.onNodeMoved( this.node_dragged ); + this.graph.afterChange(this.node_dragged); this.node_dragged = null; } //no node being dragged else { @@ -5927,6 +6247,8 @@ LGraphNode.prototype.executeAction = function(action) return; } + this.graph.beforeChange(); + //create nodes var clipboard_info = JSON.parse(data); var nodes = []; @@ -5954,6 +6276,8 @@ LGraphNode.prototype.executeAction = function(action) } this.selectNodes(nodes); + + this.graph.afterChange(); }; /** @@ -5965,7 +6289,7 @@ LGraphNode.prototype.executeAction = function(action) this.adjustMouseEvent(e); var pos = [e.canvasX, e.canvasY]; - var node = this.graph.getNodeOnPos(pos[0], pos[1]); + var node = this.graph ? this.graph.getNodeOnPos(pos[0], pos[1]) : null; if (!node) { var r = null; @@ -6034,12 +6358,14 @@ LGraphNode.prototype.executeAction = function(action) var ext = LGraphCanvas.getFileExtension(file.name).toLowerCase(); var nodetype = LiteGraph.node_types_by_file_extension[ext]; if (nodetype) { + this.graph.beforeChange(); var node = LiteGraph.createNode(nodetype.type); node.pos = [e.canvasX, e.canvasY]; this.graph.add(node); if (node.onDropFile) { node.onDropFile(file); } + this.graph.afterChange(); } } }; @@ -6048,6 +6374,10 @@ LGraphNode.prototype.executeAction = function(action) if (this.onShowNodePanel) { this.onShowNodePanel(n); } + else + { + this.showShowNodePanel(n); + } if (this.onNodeDblClicked) { this.onNodeDblClicked(n); @@ -6082,10 +6412,8 @@ LGraphNode.prototype.executeAction = function(action) * selects several nodes (or adds them to the current selection) * @method selectNodes **/ - LGraphCanvas.prototype.selectNodes = function( - nodes, - add_to_current_selection - ) { + LGraphCanvas.prototype.selectNodes = function( nodes, add_to_current_selection ) + { if (!add_to_current_selection) { this.deselectAllNodes(); } @@ -6196,8 +6524,15 @@ LGraphNode.prototype.executeAction = function(action) * @method deleteSelectedNodes **/ LGraphCanvas.prototype.deleteSelectedNodes = function() { + + this.graph.beforeChange(); + for (var i in this.selected_nodes) { var node = this.selected_nodes[i]; + + if(node.block_delete) + continue; + //autoconnect when possible (very basic, only takes into account first input-output) if(node.inputs && node.inputs.length && node.outputs && node.outputs.length && LiteGraph.isValidConnection( node.inputs[0].type, node.outputs[0].type ) && node.inputs[0].link && node.outputs[0].links && node.outputs[0].links.length ) { @@ -6217,6 +6552,7 @@ LGraphNode.prototype.executeAction = function(action) this.current_node = null; this.highlighted_links = {}; this.setDirty(true); + this.graph.afterChange(); }; /** @@ -6377,7 +6713,7 @@ LGraphNode.prototype.executeAction = function(action) * @method draw **/ LGraphCanvas.prototype.draw = function(force_canvas, force_bgcanvas) { - if (!this.canvas) { + if (!this.canvas || this.canvas.width == 0 || this.canvas.height == 0) { return; } @@ -6458,7 +6794,7 @@ LGraphNode.prototype.executeAction = function(action) if (this.bgcanvas == this.canvas) { this.drawBackCanvas(); } else { - ctx.drawImage(this.bgcanvas, 0, 0); + ctx.drawImage( this.bgcanvas, 0, 0 ); } //rendering @@ -6527,7 +6863,7 @@ LGraphNode.prototype.executeAction = function(action) this.renderLink( ctx, this.connecting_pos, - [this.canvas_mouse[0], this.canvas_mouse[1]], + [this.graph_mouse[0], this.graph_mouse[1]], null, false, null, @@ -6601,6 +6937,12 @@ LGraphNode.prototype.executeAction = function(action) ctx.restore(); } + //draws panel in the corner + if (this._graph_stack && this._graph_stack.length) { + this.drawSubgraphPanel( ctx ); + } + + if (this.onDrawOverlay) { this.onDrawOverlay(ctx); } @@ -6616,13 +6958,153 @@ LGraphNode.prototype.executeAction = function(action) } }; + /** + * draws the panel in the corner that shows subgraph properties + * @method drawSubgraphPanel + **/ + LGraphCanvas.prototype.drawSubgraphPanel = function(ctx) { + var subgraph = this.graph; + var subnode = subgraph._subgraph_node; + if(!subnode) + { + console.warn("subgraph without subnode"); + return; + } + + var num = subnode.inputs ? subnode.inputs.length : 0; + var w = 300; + var h = Math.floor(LiteGraph.NODE_SLOT_HEIGHT * 1.6); + + ctx.fillStyle = "#111"; + ctx.globalAlpha = 0.8; + ctx.beginPath(); + ctx.roundRect(10,10,w, (num + 1) * h + 50,8 ); + ctx.fill(); + ctx.globalAlpha = 1; + + ctx.fillStyle = "#888"; + ctx.font = "14px Arial"; + ctx.textAlign = "left"; + ctx.fillText( "Graph Inputs", 20, 34 ); + var pos = this.mouse; + + if( this.drawButton( w - 20, 20,20,20, "X", "#151515" ) ) + { + this.closeSubgraph(); + return; + } + + var y = 50; + ctx.font = "20px Arial"; + if(subnode.inputs) + for(var i = 0; i < subnode.inputs.length; ++i) + { + var input = subnode.inputs[i]; + if(input.not_subgraph_input) + continue; + + //input button clicked + if( this.drawButton( 20,y+2,w - 20, h - 2 ) ) + { + var type = subnode.constructor.input_node_type || "graph/input"; + this.graph.beforeChange(); + var newnode = LiteGraph.createNode( type ); + if(newnode) + { + subgraph.add( newnode ); + this.block_click = false; + this.last_click_position = null; + this.selectNodes([newnode]); + this.node_dragged = newnode; + this.dragging_canvas = false; + newnode.setProperty("name",input.name); + newnode.setProperty("type",input.type); + this.node_dragged.pos[0] = this.graph_mouse[0] - 5; + this.node_dragged.pos[1] = this.graph_mouse[1] - 5; + this.graph.afterChange(); + } + else + console.error("graph input node not found:",type); + } + + ctx.fillStyle = "#9C9"; + ctx.beginPath(); + ctx.arc(w - 16,y + h * 0.5,5,0,2*Math.PI); + ctx.fill(); + + ctx.fillStyle = "#AAA"; + ctx.fillText( input.name, 50, y + h*0.75 ); + var tw = ctx.measureText( input.name ); + ctx.fillStyle = "#777"; + ctx.fillText( input.type, 50 + tw.width + 10, y + h*0.75 ); + + y += h; + } + + //add + button + if( this.drawButton( 20,y+2,w - 20, h - 2, "+", "#151515", "#222" ) ) + { + this.showSubgraphPropertiesDialog( subnode ); + } + } + + //Draws a button into the canvas overlay and computes if it was clicked using the immediate gui paradigm + LGraphCanvas.prototype.drawButton = function( x,y,w,h, text, bgcolor, hovercolor, textcolor ) + { + var ctx = this.ctx; + bgcolor = bgcolor || LiteGraph.NODE_DEFAULT_COLOR; + hovercolor = hovercolor || "#555"; + textcolor = textcolor || LiteGraph.NODE_TEXT_COLOR; + + var pos = this.mouse; + var hover = LiteGraph.isInsideRectangle( pos[0], pos[1], x,y,w,h ); + pos = this.last_click_position; + var clicked = pos && LiteGraph.isInsideRectangle( pos[0], pos[1], x,y,w,h ); + + ctx.fillStyle = hover ? hovercolor : bgcolor; + if(clicked) + ctx.fillStyle = "#AAA"; + ctx.beginPath(); + ctx.roundRect(x,y,w,h,4 ); + ctx.fill(); + + if(text != null) + { + if(text.constructor == String) + { + ctx.fillStyle = textcolor; + ctx.textAlign = "center"; + ctx.font = ((h * 0.65)|0) + "px Arial"; + ctx.fillText( text, x + w * 0.5,y + h * 0.75 ); + ctx.textAlign = "left"; + } + } + + var was_clicked = clicked && !this.block_click; + if(clicked) + this.blockClick(); + return was_clicked; + } + + LGraphCanvas.prototype.isAreaClicked = function( x,y,w,h, hold_click ) + { + var pos = this.mouse; + var hover = LiteGraph.isInsideRectangle( pos[0], pos[1], x,y,w,h ); + pos = this.last_click_position; + var clicked = pos && LiteGraph.isInsideRectangle( pos[0], pos[1], x,y,w,h ); + var was_clicked = clicked && !this.block_click; + if(clicked && hold_click) + this.blockClick(); + return was_clicked; + } + /** * draws some useful stats in the corner of the canvas * @method renderInfo **/ LGraphCanvas.prototype.renderInfo = function(ctx, x, y) { - x = x || 0; - y = y || 0; + x = x || 10; + y = y || this.canvas.height - 80; ctx.save(); ctx.translate(x, y); @@ -6820,14 +7302,8 @@ LGraphNode.prototype.executeAction = function(action) var glow = false; this.current_node = node; - var color = - node.color || - node.constructor.color || - LiteGraph.NODE_DEFAULT_COLOR; - var bgcolor = - node.bgcolor || - node.constructor.bgcolor || - LiteGraph.NODE_DEFAULT_BGCOLOR; + var color = node.color || node.constructor.color || LiteGraph.NODE_DEFAULT_COLOR; + var bgcolor = node.bgcolor || node.constructor.bgcolor || LiteGraph.NODE_DEFAULT_BGCOLOR; //shadow and glow if (node.mouseOver) { @@ -7235,6 +7711,7 @@ LGraphNode.prototype.executeAction = function(action) if(text == null) return; + text = text.substr(0,30); //avoid weird ctx.font = "14px Courier New"; var info = ctx.measureText(text); @@ -7340,20 +7817,14 @@ LGraphNode.prototype.executeAction = function(action) ctx.shadowColor = "transparent"; if (node.onDrawBackground) { - node.onDrawBackground(ctx, this, this.canvas); + node.onDrawBackground(ctx, this, this.canvas, this.graph_mouse ); } //title bg (remember, it is rendered ABOVE the node) if (render_title || title_mode == LiteGraph.TRANSPARENT_TITLE) { //title bar if (node.onDrawTitleBar) { - node.onDrawTitleBar( - ctx, - title_height, - size, - this.ds.scale, - fgcolor - ); + node.onDrawTitleBar( ctx, title_height, size, this.ds.scale, fgcolor ); } else if ( title_mode != LiteGraph.TRANSPARENT_TITLE && (node.constructor.title_color || this.render_title_colored) @@ -7465,7 +7936,7 @@ LGraphNode.prototype.executeAction = function(action) } if (!low_quality) { ctx.font = this.title_text_font; - var title = node.getTitle(); + var title = String(node.getTitle()); if (title) { if (selected) { ctx.fillStyle = "white"; @@ -7475,11 +7946,11 @@ LGraphNode.prototype.executeAction = function(action) this.node_title_color; } if (node.flags.collapsed) { - ctx.textAlign = "center"; + ctx.textAlign = "left"; var measure = ctx.measureText(title); ctx.fillText( - title, - title_height + measure.width * 0.5, + title.substr(0,20), //avoid urls too long + title_height,// + measure.width * 0.5, LiteGraph.NODE_TITLE_TEXT_Y - title_height ); ctx.textAlign = "left"; @@ -7494,6 +7965,29 @@ LGraphNode.prototype.executeAction = function(action) } } + //subgraph box + if (!node.flags.collapsed && node.subgraph && !node.skip_subgraph_button) { + var w = LiteGraph.NODE_TITLE_HEIGHT; + var x = node.size[0] - w; + var over = LiteGraph.isInsideRectangle( this.graph_mouse[0] - node.pos[0], this.graph_mouse[1] - node.pos[1], x+2, -w+2, w-4, w-4 ); + ctx.fillStyle = over ? "#888" : "#555"; + if( shape == LiteGraph.BOX_SHAPE || low_quality) + ctx.fillRect(x+2, -w+2, w-4, w-4); + else + { + ctx.beginPath(); + ctx.roundRect(x+2, -w+2, w-4, w-4,4); + ctx.fill(); + } + ctx.fillStyle = "#333"; + ctx.beginPath(); + ctx.moveTo(x + w * 0.2, -w * 0.6); + ctx.lineTo(x + w * 0.8, -w * 0.6); + ctx.lineTo(x + w * 0.5, -w * 0.3); + ctx.fill(); + } + + //custom title render if (node.onDrawTitle) { node.onDrawTitle(ctx); } @@ -8111,7 +8605,7 @@ LGraphNode.prototype.executeAction = function(action) this.dirty_canvas = true; } ctx.fillRect(margin, y, width - margin * 2, H); - if(show_text) + if(show_text && !w.disabled) ctx.strokeRect( margin, y, width - margin * 2, H ); if (show_text) { ctx.textAlign = "center"; @@ -8129,7 +8623,7 @@ LGraphNode.prototype.executeAction = function(action) else ctx.rect(margin, posY, width - margin * 2, H ); ctx.fill(); - if(show_text) + if(show_text && !w.disabled) ctx.stroke(); ctx.fillStyle = w.value ? "#89A" : "#333"; ctx.beginPath(); @@ -8158,7 +8652,7 @@ LGraphNode.prototype.executeAction = function(action) var nvalue = (w.value - w.options.min) / range; ctx.fillStyle = active_widget == w ? "#89A" : "#678"; ctx.fillRect(margin, y, nvalue * (width - margin * 2), H); - if(show_text) + if(show_text && !w.disabled) ctx.strokeRect(margin, y, width - margin * 2, H); if (w.marker) { var marker_nvalue = (w.marker - w.options.min) / range; @@ -8187,18 +8681,22 @@ LGraphNode.prototype.executeAction = function(action) ctx.rect(margin, posY, width - margin * 2, H ); ctx.fill(); if (show_text) { - ctx.stroke(); + if(!w.disabled) + ctx.stroke(); ctx.fillStyle = text_color; - ctx.beginPath(); - ctx.moveTo(margin + 16, posY + 5); - ctx.lineTo(margin + 6, posY + H * 0.5); - ctx.lineTo(margin + 16, posY + H - 5); - ctx.fill(); - ctx.beginPath(); - ctx.moveTo(width - margin - 16, posY + 5); - ctx.lineTo(width - margin - 6, posY + H * 0.5); - ctx.lineTo(width - margin - 16, posY + H - 5); - ctx.fill(); + if(!w.disabled) + { + ctx.beginPath(); + ctx.moveTo(margin + 16, posY + 5); + ctx.lineTo(margin + 6, posY + H * 0.5); + ctx.lineTo(margin + 16, posY + H - 5); + ctx.fill(); + ctx.beginPath(); + ctx.moveTo(width - margin - 16, posY + 5); + ctx.lineTo(width - margin - 6, posY + H * 0.5); + ctx.lineTo(width - margin - 16, posY + H - 5); + ctx.fill(); + } ctx.fillStyle = secondary_text_color; ctx.fillText(w.name, margin * 2 + 5, y + H * 0.7); ctx.fillStyle = text_color; @@ -8214,8 +8712,17 @@ LGraphNode.prototype.executeAction = function(action) y + H * 0.7 ); } else { + var v = w.value; + if( w.options.values ) + { + var values = w.options.values; + if( values.constructor === Function ) + values = values(); + if(values && values.constructor !== Array) + v = values[ w.value ]; + } ctx.fillText( - w.value, + v, width - margin * 2 - 20, y + H * 0.7 ); @@ -8234,6 +8741,11 @@ LGraphNode.prototype.executeAction = function(action) ctx.rect( margin, posY, width - margin * 2, H ); ctx.fill(); if (show_text) { + ctx.save(); + ctx.beginPath(); + ctx.rect(margin, posY, width - margin * 2, H); + ctx.clip(); + ctx.stroke(); ctx.fillStyle = secondary_text_color; if (w.name != null) { @@ -8241,16 +8753,17 @@ LGraphNode.prototype.executeAction = function(action) } ctx.fillStyle = text_color; ctx.textAlign = "right"; - ctx.fillText(w.value, width - margin * 2, y + H * 0.7); + ctx.fillText(String(w.value).substr(0,30), width - margin * 2, y + H * 0.7); //30 chars max + ctx.restore(); } break; default: if (w.draw) { - w.draw(ctx, node, w, y, H); + w.draw(ctx, node, width, y, H); } break; } - posY += H + 4; + posY += (w.computeSize ? w.computeSize(width)[1] : H) + 4; ctx.globalAlpha = this.editor_alpha; } @@ -8280,145 +8793,166 @@ LGraphNode.prototype.executeAction = function(action) for (var i = 0; i < node.widgets.length; ++i) { var w = node.widgets[i]; - if(w.disabled) + if(!w || w.disabled) + continue; + var widget_height = w.computeSize ? w.computeSize(width)[1] : LiteGraph.NODE_WIDGET_HEIGHT; + //outside + if ( w != active_widget && + (x < 6 || x > width - 12 || y < w.last_y || y > w.last_y + widget_height) ) continue; - if ( w == active_widget || (x > 6 && x < width - 12 && y > w.last_y && y < w.last_y + LiteGraph.NODE_WIDGET_HEIGHT) ) { - //inside widget - switch (w.type) { - case "button": - if (event.type === "mousemove") { - break; - } - if (w.callback) { - setTimeout(function() { - w.callback(w, that, node, pos, event); - }, 20); - } - w.clicked = true; - this.dirty_canvas = true; - break; - case "slider": - var range = w.options.max - w.options.min; - var nvalue = Math.clamp((x - 10) / (width - 20), 0, 1); - w.value = - w.options.min + - (w.options.max - w.options.min) * nvalue; - if (w.callback) { - setTimeout(function() { - inner_value_change(w, w.value); - }, 20); - } - this.dirty_canvas = true; - break; - case "number": - case "combo": - var old_value = w.value; - if (event.type == "mousemove" && w.type == "number") { - w.value += - event.deltaX * 0.1 * (w.options.step || 1); - if ( - w.options.min != null && - w.value < w.options.min - ) { - w.value = w.options.min; - } - if ( - w.options.max != null && - w.value > w.options.max - ) { - w.value = w.options.max; - } - } else if (event.type == "mousedown") { - var values = w.options.values; - if (values && values.constructor === Function) { - values = w.options.values(w, node); - } - var delta = x < 40 ? -1 : x > width - 40 ? 1 : 0; - if (w.type == "number") { - w.value += delta * 0.1 * (w.options.step || 1); - if ( - w.options.min != null && - w.value < w.options.min - ) { - w.value = w.options.min; - } - if ( - w.options.max != null && - w.value > w.options.max - ) { - w.value = w.options.max; - } - } else if (delta) { - var index = values.indexOf(w.value) + delta; - if (index >= values.length) { - index = 0; - } - if (index < 0) { - index = values.length - 1; - } - w.value = values[index]; - } else { - var menu = new LiteGraph.ContextMenu( - values, - { - scale: Math.max(1, this.ds.scale), - event: event, - className: "dark", - callback: inner_clicked.bind(w) - }, - ref_window - ); - function inner_clicked(v, option, event) { - this.value = v; - inner_value_change(this, v); - that.dirty_canvas = true; - return false; - } - } - } //mousedown + var old_value = w.value; - if( old_value != w.value ) - setTimeout( - function() { + //if ( w == active_widget || (x > 6 && x < width - 12 && y > w.last_y && y < w.last_y + widget_height) ) { + //inside widget + switch (w.type) { + case "button": + if (event.type === "mousemove") { + break; + } + if (w.callback) { + setTimeout(function() { + w.callback(w, that, node, pos, event); + }, 20); + } + w.clicked = true; + this.dirty_canvas = true; + break; + case "slider": + var range = w.options.max - w.options.min; + var nvalue = Math.clamp((x - 10) / (width - 20), 0, 1); + w.value = + w.options.min + + (w.options.max - w.options.min) * nvalue; + if (w.callback) { + setTimeout(function() { + inner_value_change(w, w.value); + }, 20); + } + this.dirty_canvas = true; + break; + case "number": + case "combo": + var old_value = w.value; + if (event.type == "mousemove" && w.type == "number") { + w.value += event.deltaX * 0.1 * (w.options.step || 1); + if ( w.options.min != null && w.value < w.options.min ) { + w.value = w.options.min; + } + if ( w.options.max != null && w.value > w.options.max ) { + w.value = w.options.max; + } + } else if (event.type == "mousedown") { + var values = w.options.values; + if (values && values.constructor === Function) { + values = w.options.values(w, node); + } + var values_list = null; + + if( w.type != "number") + values_list = values.constructor === Array ? values : Object.keys(values); + + var delta = x < 40 ? -1 : x > width - 40 ? 1 : 0; + if (w.type == "number") { + w.value += delta * 0.1 * (w.options.step || 1); + if ( w.options.min != null && w.value < w.options.min ) { + w.value = w.options.min; + } + if ( w.options.max != null && w.value > w.options.max ) { + w.value = w.options.max; + } + } else if (delta) { //clicked in arrow, used for combos + var index = -1; + this.last_mouseclick = 0; //avoids dobl click event + if(values.constructor === Object) + index = values_list.indexOf( String( w.value ) ) + delta; + else + index = values_list.indexOf( w.value ) + delta; + if (index >= values_list.length) { + index = values_list.length - 1; + } + if (index < 0) { + index = 0; + } + if( values.constructor === Array ) + w.value = values[index]; + else + w.value = index; + } else { //combo clicked + var text_values = values != values_list ? Object.values(values) : values; + var menu = new LiteGraph.ContextMenu(text_values, { + scale: Math.max(1, this.ds.scale), + event: event, + className: "dark", + callback: inner_clicked.bind(w) + }, + ref_window); + function inner_clicked(v, option, event) { + if(values != values_list) + v = text_values.indexOf(v); + this.value = v; + inner_value_change(this, v); + that.dirty_canvas = true; + return false; + } + } + } //end mousedown + else if(event.type == "mouseup" && w.type == "number") + { + var delta = x < 40 ? -1 : x > width - 40 ? 1 : 0; + if (event.click_time < 200 && delta == 0) { + this.prompt("Value",w.value,function(v) { + this.value = Number(v); inner_value_change(this, this.value); }.bind(w), - 20 - ); - this.dirty_canvas = true; - break; - case "toggle": - if (event.type == "mousedown") { - w.value = !w.value; - setTimeout(function() { - inner_value_change(w, w.value); - }, 20); - } - break; - case "string": - case "text": - if (event.type == "mousedown") { - this.prompt( - "Value", - w.value, - function(v) { - this.value = v; - inner_value_change(this, v); - }.bind(w), - event - ); - } - break; - default: - if (w.mouse) { - w.mouse(ctx, event, [x, y], node); - } - break; - } //end switch + event); + } + } - return w; - } - } + if( old_value != w.value ) + setTimeout( + function() { + inner_value_change(this, this.value); + }.bind(w), + 20 + ); + this.dirty_canvas = true; + break; + case "toggle": + if (event.type == "mousedown") { + w.value = !w.value; + setTimeout(function() { + inner_value_change(w, w.value); + }, 20); + } + break; + case "string": + case "text": + if (event.type == "mousedown") { + this.prompt("Value",w.value,function(v) { + this.value = v; + inner_value_change(this, v); + }.bind(w), + event); + } + break; + default: + if (w.mouse) { + this.dirty_canvas = w.mouse(event, [x, y], node); + } + break; + } //end switch + + //value changed + if( old_value != w.value ) + { + if(node.onWidgetChanged) + node.onWidgetChanged( w.name,w.value,old_value,w ); + node.graph._version++; + } + + return w; + }//end for function inner_value_change(widget, value) { widget.value = value; @@ -8615,12 +9149,18 @@ LGraphNode.prototype.executeAction = function(action) LGraphCanvas.onMenuAdd = function(node, options, e, prev_menu, callback) { var canvas = LGraphCanvas.active_canvas; var ref_window = canvas.getCanvasWindow(); + var graph = canvas.graph; + if(!graph) + return; - var values = LiteGraph.getNodeTypesCategories( canvas.filter ); + var values = LiteGraph.getNodeTypesCategories( canvas.filter || graph.filter ); var entries = []; for (var i=0; i < values.length; i++) { if (values[i]) { - entries.push({ value: values[i], content: values[i], has_submenu: true }); + var name = values[i]; + if(name.indexOf("::") != -1) //in case it has a namespace like "shader::math/rand" it hides the namespace + name = name.split("::")[1]; + entries.push({ value: values[i], content: name, has_submenu: true }); } } @@ -8629,7 +9169,7 @@ LGraphNode.prototype.executeAction = function(action) function inner_clicked(v, option, e) { var category = v.value; - var node_types = LiteGraph.getNodeTypesInCategory( category, canvas.filter ); + var node_types = LiteGraph.getNodeTypesInCategory( category, canvas.filter || graph.filter ); var values = []; for (var i=0; i < node_types.length; i++) { if (!node_types[i].skip_list) { @@ -8646,6 +9186,7 @@ LGraphNode.prototype.executeAction = function(action) function inner_create(v, e) { var first_event = prev_menu.getFirstEvent(); + canvas.graph.beforeChange(); var node = LiteGraph.createNode(v.value); if (node) { node.pos = canvas.convertEventToCanvasOffset(first_event); @@ -8653,6 +9194,7 @@ LGraphNode.prototype.executeAction = function(action) } if(callback) callback(node); + canvas.graph.afterChange(); } return false; @@ -8707,6 +9249,7 @@ LGraphNode.prototype.executeAction = function(action) } if (!entries.length) { + console.log("no input entries"); return; } @@ -8731,8 +9274,10 @@ LGraphNode.prototype.executeAction = function(action) } if (v.value) { + node.graph.beforeChange(); node.addInput(v.value[0], v.value[1], v.value[2]); node.setDirtyCanvas(true, true); + node.graph.afterChange(); } } @@ -8839,8 +9384,10 @@ LGraphNode.prototype.executeAction = function(action) }); return false; } else { + node.graph.beforeChange(); node.addOutput(v.value[0], v.value[1], v.value[2]); node.setDirtyCanvas(true, true); + node.graph.afterChange(); } } @@ -8867,6 +9414,10 @@ LGraphNode.prototype.executeAction = function(action) var value = node.properties[i] !== undefined ? node.properties[i] : " "; if( typeof value == "object" ) value = JSON.stringify(value); + var info = node.getPropertyInfo(i); + if(info.type == "enum" || info.type == "combo") + value = LGraphCanvas.getPropertyPrintableValue( value, info.values ); + //value could contain invalid html characters, clean that value = LGraphCanvas.decodeHTML(value); entries.push({ @@ -8920,6 +9471,8 @@ LGraphNode.prototype.executeAction = function(action) return; } node.size = node.computeSize(); + if (node.onResize) + node.onResize(node.size); node.setDirtyCanvas(true, true); }; @@ -9267,6 +9820,7 @@ LGraphNode.prototype.executeAction = function(action) name = extra.type; } + graphcanvas.graph.beforeChange(); var node = LiteGraph.createNode(name); if (node) { node.pos = graphcanvas.convertEventToCanvasOffset( @@ -9305,6 +9859,8 @@ LGraphNode.prototype.executeAction = function(action) if (extra.data.json) { node.configure(extra.data.json); } + + graphcanvas.graph.afterChange(); } } } @@ -9421,11 +9977,7 @@ LGraphNode.prototype.executeAction = function(action) return dialog; }; - LGraphCanvas.prototype.showEditPropertyValue = function( - node, - property, - options - ) { + LGraphCanvas.prototype.showEditPropertyValue = function( node, property, options ) { if (!node || node.properties[property] === undefined) { return; } @@ -9433,37 +9985,20 @@ LGraphNode.prototype.executeAction = function(action) options = options || {}; var that = this; - var type = "string"; - - if (node.properties[property] !== null) { - type = typeof node.properties[property]; - } - - var info = null; - if (node.getPropertyInfo) { - info = node.getPropertyInfo(property); - } - if (node.properties_info) { - for (var i = 0; i < node.properties_info.length; ++i) { - if (node.properties_info[i].name == property) { - info = node.properties_info[i]; - break; - } - } - } - - if (info !== undefined && info !== null && info.type) { - type = info.type; - } + var info = node.getPropertyInfo(property); + var type = info.type; var input_html = ""; if (type == "string" || type == "number" || type == "array" || type == "object") { input_html = ""; - } else if (type == "enum" && info.values) { + } else if ( (type == "enum" || type == "combo") && info.values) { input_html = ""; + var textarea = panel.content.querySelector("textarea"); + textarea.value = node.properties[propname]; + textarea.addEventListener("keydown", function(e){ + //console.log(e); + if(e.code == "Enter" && e.ctrlKey ) + { + console.log("Assigned"); + node.setProperty(propname, textarea.value); + } + }); + textarea.style.height = "calc(100% - 40px)"; + } + var assign = that.createButton( "Assign", null, function(){ + node.setProperty(propname, textarea.value); + }); + panel.content.appendChild(assign); + var button = that.createButton( "Close", null, function(){ + panel.style.height = ""; + inner_refresh(); + }); + button.style.float = "right"; + panel.content.appendChild(button); + } + + inner_refresh(); + + this.canvas.parentNode.appendChild( panel ); + } + + LGraphCanvas.prototype.showSubgraphPropertiesDialog = function(node) + { + console.log("showing subgraph properties dialog"); + + var old_panel = this.canvas.parentNode.querySelector(".subgraph_dialog"); + if(old_panel) + old_panel.close(); + + var panel = this.createPanel("Subgraph Inputs",{closable:true, width: 500}); + panel.node = node; + panel.classList.add("subgraph_dialog"); + + function inner_refresh() + { + panel.clear(); + + //show currents + if(node.inputs) + for(var i = 0; i < node.inputs.length; ++i) + { + var input = node.inputs[i]; + if(input.not_subgraph_input) + continue; + var html = " "; + var elem = panel.addHTML(html,"subgraph_property"); + elem.dataset["name"] = input.name; + elem.dataset["slot"] = i; + elem.querySelector(".name").innerText = input.name; + elem.querySelector(".type").innerText = input.type; + elem.querySelector("button").addEventListener("click",function(e){ + node.removeInput( Number( this.parentNode.dataset["slot"] ) ); + inner_refresh(); + }); + } + } + + //add extra + var html = " + NameType"; + var elem = panel.addHTML(html,"subgraph_property extra", true); + elem.querySelector("button").addEventListener("click", function(e){ + var elem = this.parentNode; + var name = elem.querySelector(".name").value; + var type = elem.querySelector(".type").value; + if(!name || node.findInputSlot(name) != -1) + return; + node.addInput(name,type); + elem.querySelector(".name").value = ""; + elem.querySelector(".type").value = ""; + inner_refresh(); + }); + + inner_refresh(); + this.canvas.parentNode.appendChild(panel); + return panel; + } + + LGraphCanvas.prototype.checkPanels = function() + { + if(!this.canvas) + return; + var panels = this.canvas.parentNode.querySelectorAll(".litegraph.dialog"); + for(var i = 0; i < panels.length; ++i) + { + var panel = panels[i]; + if( !panel.node ) + continue; + if( !panel.node.graph || panel.graph != this.graph ) + panel.close(); + } + } + LGraphCanvas.onMenuNodeCollapse = function(value, options, e, menu, node) { + node.graph.beforeChange(node); node.collapse(); + node.graph.afterChange(node); }; LGraphCanvas.onMenuNodePin = function(value, options, e, menu, node) { @@ -9705,7 +10617,9 @@ LGraphNode.prototype.executeAction = function(action) if (!node) { return; } + node.graph.beforeChange(node); node.shape = v; + node.graph.afterChange(node); node.setDirtyCanvas(true); } @@ -9721,7 +10635,30 @@ LGraphNode.prototype.executeAction = function(action) return; } - node.graph.remove(node); + var graph = node.graph; + graph.beforeChange(); + graph.remove(node); + graph.afterChange(); + node.setDirtyCanvas(true, true); + }; + + LGraphCanvas.onMenuNodeToSubgraph = function(value, options, e, menu, node) { + var graph = node.graph; + var graphcanvas = LGraphCanvas.active_canvas; + if(!graphcanvas) //?? + return; + + var nodes_list = Object.values( graphcanvas.selected_nodes || {} ); + if( !nodes_list.length ) + nodes_list = [ node ]; + + var subgraph_node = LiteGraph.createNode("graph/subgraph"); + subgraph_node.pos = node.pos.concat(); + graph.add(subgraph_node); + + subgraph_node.buildFromNodes( nodes_list ); + + graphcanvas.deselectAllNodes(); node.setDirtyCanvas(true, true); }; @@ -9734,7 +10671,11 @@ LGraphNode.prototype.executeAction = function(action) return; } newnode.pos = [node.pos[0] + 5, node.pos[1] + 5]; + + node.graph.beforeChange(); node.graph.add(newnode); + node.graph.afterChange(); + node.setDirtyCanvas(true, true); }; @@ -9858,7 +10799,7 @@ LGraphNode.prototype.executeAction = function(action) } if (node.getExtraMenuOptions) { - var extra = node.getExtraMenuOptions(this); + var extra = node.getExtraMenuOptions(this, options); if (extra) { extra.push(null); options = extra.concat(options); @@ -9871,12 +10812,18 @@ LGraphNode.prototype.executeAction = function(action) callback: LGraphCanvas.onMenuNodeClone }); } - if (node.removable !== false) { - options.push(null, { - content: "Remove", - callback: LGraphCanvas.onMenuNodeRemove - }); - } + + if(0) //TODO + options.push({ + content: "To Subgraph", + callback: LGraphCanvas.onMenuNodeToSubgraph + }); + + options.push(null, { + content: "Remove", + disabled: !(node.removable !== false && !node.block_delete ), + callback: LGraphCanvas.onMenuNodeRemove + }); if (node.graph && node.graph.onGetNodeMenuOptions) { node.graph.onGetNodeMenuOptions(options, node); @@ -9931,25 +10878,30 @@ LGraphNode.prototype.executeAction = function(action) if (slot) { //on slot menu_info = []; - if ( - slot && - slot.output && - slot.output.links && - slot.output.links.length - ) { - menu_info.push({ content: "Disconnect Links", slot: slot }); + if (node.getSlotMenuOptions) { + menu_info = node.getSlotMenuOptions(slot); + } else { + if ( + slot && + slot.output && + slot.output.links && + slot.output.links.length + ) { + menu_info.push({ content: "Disconnect Links", slot: slot }); + } + var _slot = slot.input || slot.output; + menu_info.push( + _slot.locked + ? "Cannot remove" + : { content: "Remove Slot", slot: slot } + ); + menu_info.push( + _slot.nameLocked + ? "Cannot rename" + : { content: "Rename Slot", slot: slot } + ); + } - var _slot = slot.input || slot.output; - menu_info.push( - _slot.locked - ? "Cannot remove" - : { content: "Remove Slot", slot: slot } - ); - menu_info.push( - _slot.nameLocked - ? "Cannot rename" - : { content: "Rename Slot", slot: slot } - ); options.title = (slot.input ? slot.input.type : slot.output.type) || "*"; if (slot.input && slot.input.type == LiteGraph.ACTION) { @@ -10044,8 +10996,8 @@ LGraphNode.prototype.executeAction = function(action) //API ************************************************* //like rect but rounded corners - if (this.CanvasRenderingContext2D) { - CanvasRenderingContext2D.prototype.roundRect = function( + if (typeof(window) != "undefined" && window.CanvasRenderingContext2D) { + window.CanvasRenderingContext2D.prototype.roundRect = function( x, y, width, @@ -10241,11 +11193,12 @@ LGraphNode.prototype.executeAction = function(action) } } - if ( - options.event && - options.event.constructor !== MouseEvent && - options.event.constructor !== CustomEvent && - options.event.constructor !== PointerEvent + var eventClass = null; + if(options.event) //use strings because comparing classes between windows doesnt work + eventClass = options.event.constructor.name; + if ( eventClass !== "MouseEvent" && + eventClass !== "CustomEvent" && + eventClass !== "PointerEvent" ) { console.error( "Event passed to ContextMenu is not of type MouseEvent or CustomEvent. Ignoring it." @@ -10386,11 +11339,13 @@ LGraphNode.prototype.executeAction = function(action) var body_rect = document.body.getBoundingClientRect(); var root_rect = root.getBoundingClientRect(); + if(body_rect.height == 0) + console.error("document.body height is 0. That is dangerous, set html,body { height: 100%; }"); - if (left > body_rect.width - root_rect.width - 10) { + if (body_rect.width && left > body_rect.width - root_rect.width - 10) { left = body_rect.width - root_rect.width - 10; } - if (top > body_rect.height - root_rect.height - 10) { + if (body_rect.height && top > body_rect.height - root_rect.height - 10) { top = body_rect.height - root_rect.height - 10; } } diff --git a/src/nodes/.gltextures.js.swp b/src/nodes/.gltextures.js.swp deleted file mode 100644 index 5e286edeb..000000000 Binary files a/src/nodes/.gltextures.js.swp and /dev/null differ diff --git a/src/nodes/audio.js b/src/nodes/audio.js index 1c63ea2d1..ca35c6990 100644 --- a/src/nodes/audio.js +++ b/src/nodes/audio.js @@ -275,6 +275,7 @@ } } + LGAudioSource.desc = "Plays an audio file"; LGAudioSource["@src"] = { widget: "resource" }; LGAudioSource.supported_extensions = ["wav", "ogg", "mp3"]; @@ -290,7 +291,7 @@ } if (this.properties.autoplay) { - this.playBuffer(this._audiobuffer); + this.playBuffer(this._audiobuffer); } }; @@ -345,8 +346,10 @@ if (v === undefined) { continue; } - if (input.name == "gain") { + if (input.name == "gain") this.audionode.gain.value = v; + else if (input.name == "src") { + this.setProperty("src",v); } else if (input.name == "playbackRate") { this.properties.playbackRate = v; for (var j = 0; j < this._audionodes.length; ++j) { @@ -401,7 +404,10 @@ audionode.playbackRate.value = this.properties.playbackRate; this._audionodes.push(audionode); audionode.connect(this.audionode); //connect to gain - this._audionodes.push(audionode); + + this._audionodes.push(audionode); + + this.trigger("start"); audionode.onended = function() { //console.log("ended!"); @@ -458,13 +464,14 @@ LGAudioSource.prototype.onGetInputs = function() { return [ ["playbackRate", "number"], + ["src","string"], ["Play", LiteGraph.ACTION], ["Stop", LiteGraph.ACTION] ]; }; LGAudioSource.prototype.onGetOutputs = function() { - return [["buffer", "audiobuffer"], ["ended", LiteGraph.EVENT]]; + return [["buffer", "audiobuffer"], ["start", LiteGraph.EVENT], ["ended", LiteGraph.EVENT]]; }; LGAudioSource.prototype.onDropFile = function(file) { @@ -1357,7 +1364,7 @@ LiteGraph.registerNodeType("audio/waveShaper", LGAudioWaveShaper); } }; - LGAudioScript["@code"] = { widget: "code" }; + LGAudioScript["@code"] = { widget: "code", type: "code" }; LGAudioScript.prototype.onStart = function() { this.audionode.onaudioprocess = this._callback; diff --git a/src/nodes/base.js b/src/nodes/base.js index 63a1ec03d..51414c016 100755 --- a/src/nodes/base.js +++ b/src/nodes/base.js @@ -32,18 +32,15 @@ this.subgraph.onTrigger = this.onSubgraphTrigger.bind(this); + //nodes input node added inside this.subgraph.onInputAdded = this.onSubgraphNewInput.bind(this); this.subgraph.onInputRenamed = this.onSubgraphRenamedInput.bind(this); - this.subgraph.onInputTypeChanged = this.onSubgraphTypeChangeInput.bind( - this - ); + this.subgraph.onInputTypeChanged = this.onSubgraphTypeChangeInput.bind(this); this.subgraph.onInputRemoved = this.onSubgraphRemovedInput.bind(this); this.subgraph.onOutputAdded = this.onSubgraphNewOutput.bind(this); this.subgraph.onOutputRenamed = this.onSubgraphRenamedOutput.bind(this); - this.subgraph.onOutputTypeChanged = this.onSubgraphTypeChangeOutput.bind( - this - ); + this.subgraph.onOutputTypeChanged = this.onSubgraphTypeChangeOutput.bind(this); this.subgraph.onOutputRemoved = this.onSubgraphRemovedOutput.bind(this); } @@ -55,6 +52,7 @@ return [["enabled", "boolean"]]; }; + /* Subgraph.prototype.onDrawTitle = function(ctx) { if (this.flags.collapsed) { return; @@ -71,6 +69,7 @@ ctx.lineTo(x + w * 0.5, -w * 0.3); ctx.fill(); }; + */ Subgraph.prototype.onDblClick = function(e, pos, graphcanvas) { var that = this; @@ -79,6 +78,7 @@ }, 10); }; + /* Subgraph.prototype.onMouseDown = function(e, pos, graphcanvas) { if ( !this.flags.collapsed && @@ -91,6 +91,7 @@ }, 10); } }; + */ Subgraph.prototype.onAction = function(action, param) { this.subgraph.onAction(action, param); @@ -130,6 +131,46 @@ } }; + Subgraph.prototype.onDrawBackground = function(ctx, graphcanvas, canvas, pos) + { + if(this.flags.collapsed) + return; + + var y = this.size[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5; + + //button + var over = LiteGraph.isInsideRectangle(pos[0],pos[1],this.pos[0],this.pos[1] + y,this.size[0],LiteGraph.NODE_TITLE_HEIGHT); + ctx.fillStyle = over ? "#555" : "#222"; + ctx.beginPath(); + if (this._shape == LiteGraph.BOX_SHAPE) + ctx.rect(0, y, this.size[0]+1, LiteGraph.NODE_TITLE_HEIGHT); + else + ctx.roundRect( 0, y, this.size[0]+1, LiteGraph.NODE_TITLE_HEIGHT, 0, 8); + ctx.fill(); + + //button + ctx.textAlign = "center"; + ctx.font = "24px Arial"; + ctx.fillStyle = over ? "#DDD" : "#999"; + ctx.fillText( "+", this.size[0] * 0.5, y + 24 ); + } + + Subgraph.prototype.onMouseDown = function(e, localpos, graphcanvas) + { + var y = this.size[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5; + if(localpos[1] > y) + { + graphcanvas.showSubgraphPropertiesDialog(this); + } + } + + Subgraph.prototype.computeSize = function() + { + var num_inputs = this.inputs ? this.inputs.length : 0; + var num_outputs = this.outputs ? this.outputs.length : 0; + return [ 200, Math.max(num_inputs,num_outputs) * LiteGraph.NODE_SLOT_HEIGHT + LiteGraph.NODE_TITLE_HEIGHT ]; + } + //**** INPUTS *********************************** Subgraph.prototype.onSubgraphTrigger = function(event, param) { var slot = this.findOutputSlot(event); @@ -224,7 +265,7 @@ }; Subgraph.prototype.serialize = function() { - var data = LGraphNode.prototype.serialize.call(this); + var data = LiteGraph.LGraphNode.prototype.serialize.call(this); data.subgraph = this.subgraph.serialize(); return data; }; @@ -240,6 +281,95 @@ return node; }; + Subgraph.prototype.buildFromNodes = function(nodes) + { + //clear all? + //TODO + + //nodes that connect data between parent graph and subgraph + var subgraph_inputs = []; + var subgraph_outputs = []; + + //mark inner nodes + var ids = {}; + var min_x = 0; + var max_x = 0; + for(var i = 0; i < nodes.length; ++i) + { + var node = nodes[i]; + ids[ node.id ] = node; + min_x = Math.min( node.pos[0], min_x ); + max_x = Math.max( node.pos[0], min_x ); + } + + var last_input_y = 0; + var last_output_y = 0; + + for(var i = 0; i < nodes.length; ++i) + { + var node = nodes[i]; + //check inputs + if( node.inputs ) + for(var j = 0; j < node.inputs.length; ++j) + { + var input = node.inputs[j]; + if( !input || !input.link ) + continue; + var link = node.graph.links[ input.link ]; + if(!link) + continue; + if( ids[ link.origin_id ] ) + continue; + //this.addInput(input.name,link.type); + this.subgraph.addInput(input.name,link.type); + /* + var input_node = LiteGraph.createNode("graph/input"); + this.subgraph.add( input_node ); + input_node.pos = [min_x - 200, last_input_y ]; + last_input_y += 100; + */ + } + + //check outputs + if( node.outputs ) + for(var j = 0; j < node.outputs.length; ++j) + { + var output = node.outputs[j]; + if( !output || !output.links || !output.links.length ) + continue; + var is_external = false; + for(var k = 0; k < output.links.length; ++k) + { + var link = node.graph.links[ output.links[k] ]; + if(!link) + continue; + if( ids[ link.target_id ] ) + continue; + is_external = true; + break; + } + if(!is_external) + continue; + //this.addOutput(output.name,output.type); + /* + var output_node = LiteGraph.createNode("graph/output"); + this.subgraph.add( output_node ); + output_node.pos = [max_x + 50, last_output_y ]; + last_output_y += 100; + */ + } + } + + //detect inputs and outputs + //split every connection in two data_connection nodes + //keep track of internal connections + //connect external connections + + //clone nodes inside subgraph and try to reconnect them + + //connect edge subgraph nodes to extarnal connections nodes + } + LiteGraph.Subgraph = Subgraph; LiteGraph.registerNodeType("graph/subgraph", Subgraph); @@ -297,16 +427,27 @@ this.updateType(); } + //ensures the type in the node output and the type in the associated graph input are the same GraphInput.prototype.updateType = function() { var type = this.properties.type; this.type_widget.value = type; + + //update output + if(this.outputs[0].type != type) + { + if (!LiteGraph.isValidConnection(this.outputs[0].type,type)) + this.disconnectOutput(0); + this.outputs[0].type = type; + } + + //update widget if(type == "number") { this.value_widget.type = "number"; this.value_widget.value = 0; } - else if(type == "bool") + else if(type == "boolean") { this.value_widget.type = "toggle"; this.value_widget.value = true; @@ -322,8 +463,14 @@ this.value_widget.value = null; } this.properties.value = this.value_widget.value; + + //update graph + if (this.graph && this.name_in_graph) { + this.graph.changeInputType(this.name_in_graph, type); + } } + //this is executed AFTER the property has changed GraphInput.prototype.onPropertyChanged = function(name,v) { if( name == "name" ) @@ -345,8 +492,7 @@ } else if( name == "type" ) { - v = v || ""; - this.updateType(v); + this.updateType(); } else if( name == "value" ) { @@ -372,8 +518,10 @@ var data = this.graph.inputs[name]; if (!data) { this.setOutputData(0, this.properties.value ); + return; } - this.setOutputData(0, data.value === undefined ? this.properties.value : data.value); + + this.setOutputData(0, data.value !== undefined ? data.value : this.properties.value ); }; GraphInput.prototype.onRemoved = function() { @@ -421,6 +569,8 @@ if (v == "action" || v == "event") { v = LiteGraph.ACTION; } + if (!LiteGraph.isValidConnection(that.inputs[0].type,v)) + that.disconnectInput(0); that.inputs[0].type = v; if (that.name_in_graph) { //already added @@ -434,27 +584,8 @@ enumerable: true }); - this.name_widget = this.addWidget( - "text", - "Name", - this.properties.name, - function(v) { - if (!v) { - return; - } - that.properties.name = v; - } - ); - this.type_widget = this.addWidget( - "text", - "Type", - this.properties.type, - function(v) { - v = v || ""; - that.properties.type = v; - } - ); - + this.name_widget = this.addWidget("text","Name",this.properties.name,"name"); + this.type_widget = this.addWidget("text","Type",this.properties.type,"type"); this.widgets_up = true; this.size = [180, 60]; } @@ -493,6 +624,9 @@ function ConstantNumber() { this.addOutput("value", "number"); this.addProperty("value", 1.0); + this.widget = this.addWidget("number","value",1,"value"); + this.widgets_up = true; + this.size = [180, 30]; } ConstantNumber.title = "Const Number"; @@ -509,9 +643,10 @@ return this.title; }; - ConstantNumber.prototype.setValue = function(v) { - this.properties.value = v; - }; + ConstantNumber.prototype.setValue = function(v) + { + this.setProperty("value",v); + } ConstantNumber.prototype.onDrawBackground = function(ctx) { //show the current value @@ -520,47 +655,170 @@ LiteGraph.registerNodeType("basic/const", ConstantNumber); + function ConstantBoolean() { + this.addOutput("", "boolean"); + this.addProperty("value", true); + this.widget = this.addWidget("toggle","value",true,"value"); + this.widgets_up = true; + this.size = [140, 30]; + } + + ConstantBoolean.title = "Const Boolean"; + ConstantBoolean.desc = "Constant boolean"; + ConstantBoolean.prototype.getTitle = ConstantNumber.prototype.getTitle; + + ConstantBoolean.prototype.onExecute = function() { + this.setOutputData(0, this.properties["value"]); + }; + + ConstantBoolean.prototype.setValue = ConstantNumber.prototype.setValue; + + ConstantBoolean.prototype.onGetInputs = function() { + return [["toggle", LiteGraph.ACTION]]; + }; + + ConstantBoolean.prototype.onAction = function(action) + { + this.setValue( !this.properties.value ); + } + + LiteGraph.registerNodeType("basic/boolean", ConstantBoolean); + function ConstantString() { this.addOutput("", "string"); this.addProperty("value", ""); - this.widget = this.addWidget( - "text", - "value", - "", - this.setValue.bind(this) - ); + this.widget = this.addWidget("text","value","","value"); //link to property value this.widgets_up = true; - this.size = [100, 30]; + this.size = [180, 30]; } ConstantString.title = "Const String"; ConstantString.desc = "Constant string"; - ConstantString.prototype.setValue = function(v) { - this.properties.value = v; - }; - - ConstantString.prototype.onPropertyChanged = function(name, value) { - this.widget.value = value; - }; - ConstantString.prototype.getTitle = ConstantNumber.prototype.getTitle; ConstantString.prototype.onExecute = function() { this.setOutputData(0, this.properties["value"]); }; + ConstantString.prototype.setValue = ConstantNumber.prototype.setValue; + + ConstantString.prototype.onDropFile = function(file) + { + var that = this; + var reader = new FileReader(); + reader.onload = function(e) + { + that.setProperty("value",e.target.result); + } + reader.readAsText(file); + } + LiteGraph.registerNodeType("basic/string", ConstantString); + function ConstantFile() { + this.addInput("url", ""); + this.addOutput("", ""); + this.addProperty("url", ""); + this.addProperty("type", "text"); + this.widget = this.addWidget("text","url","","url"); + this._data = null; + } + + ConstantFile.title = "Const File"; + ConstantFile.desc = "Fetches a file from an url"; + ConstantFile["@type"] = { type: "enum", values: ["text","arraybuffer","blob","json"] }; + + ConstantFile.prototype.onPropertyChanged = function(name, value) { + if (name == "url") + { + if( value == null || value == "") + this._data = null; + else + { + this.fetchFile(value); + } + } + } + + ConstantFile.prototype.onExecute = function() { + var url = this.getInputData(0) || this.properties.url; + if(url && (url != this._url || this._type != this.properties.type)) + this.fetchFile(url); + this.setOutputData(0, this._data ); + }; + + ConstantFile.prototype.setValue = ConstantNumber.prototype.setValue; + + ConstantFile.prototype.fetchFile = function(url) { + var that = this; + if(!url || url.constructor !== String) + { + that._data = null; + that.boxcolor = null; + return; + } + + this._url = url; + this._type = this.properties.type; + if (url.substr(0, 4) == "http" && LiteGraph.proxy) { + url = LiteGraph.proxy + url.substr(url.indexOf(":") + 3); + } + fetch(url) + .then(function(response) { + if(!response.ok) + throw new Error("File not found"); + + if(that.properties.type == "arraybuffer") + return response.arrayBuffer(); + else if(that.properties.type == "text") + return response.text(); + else if(that.properties.type == "json") + return response.json(); + else if(that.properties.type == "blob") + return response.blob(); + }) + .then(function(data) { + that._data = data; + that.boxcolor = "#AEA"; + }) + .catch(function(error) { + that._data = null; + that.boxcolor = "red"; + console.error("error fetching file:",url); + }); + }; + + ConstantFile.prototype.onDropFile = function(file) + { + var that = this; + this._url = file.name; + this._type = this.properties.type; + this.properties.url = file.name; + var reader = new FileReader(); + reader.onload = function(e) + { + that.boxcolor = "#AEA"; + var v = e.target.result; + if( that.properties.type == "json" ) + v = JSON.parse(v); + that._data = v; + } + if(that.properties.type == "arraybuffer") + reader.readAsArrayBuffer(file); + else if(that.properties.type == "text" || that.properties.type == "json") + reader.readAsText(file); + else if(that.properties.type == "blob") + return reader.readAsBinaryString(file); + } + + LiteGraph.registerNodeType("basic/file", ConstantFile); + + //to store json objects function ConstantData() { this.addOutput("", ""); this.addProperty("value", ""); - this.widget = this.addWidget( - "text", - "json", - "", - this.setValue.bind(this) - ); + this.widget = this.addWidget("text","json","","value"); this.widgets_up = true; this.size = [140, 30]; this._value = null; @@ -569,11 +827,6 @@ ConstantData.title = "Const Data"; ConstantData.desc = "Constant Data"; - ConstantData.prototype.setValue = function(v) { - this.properties.value = v; - this.onPropertyChanged("value", v); - }; - ConstantData.prototype.onPropertyChanged = function(name, value) { this.widget.value = value; if (value == null || value == "") { @@ -592,18 +845,120 @@ this.setOutputData(0, this._value); }; + ConstantData.prototype.setValue = ConstantNumber.prototype.setValue; + LiteGraph.registerNodeType("basic/data", ConstantData); + //to store json objects + function ConstantArray() { + this.addInput("", ""); + this.addOutput("", "array"); + this.addOutput("length", "number"); + this.addProperty("value", ""); + this.widget = this.addWidget("text","array","","value"); + this.widgets_up = true; + this.size = [140, 50]; + this._value = null; + } + + ConstantArray.title = "Const Array"; + ConstantArray.desc = "Constant Array"; + + ConstantArray.prototype.onPropertyChanged = function(name, value) { + this.widget.value = value; + if (value == null || value == "") { + return; + } + + try { + if(value[0] != "[") + this._value = JSON.parse("[" + value + "]"); + else + this._value = JSON.parse(value); + this.boxcolor = "#AEA"; + } catch (err) { + this.boxcolor = "red"; + } + }; + + ConstantArray.prototype.onExecute = function() { + var v = this.getInputData(0); + if(v && v.length) //clone + { + if(!this._value) + this._value = new Array(); + this._value.length = v.length; + for(var i = 0; i < v.length; ++i) + this._value[i] = v[i]; + } + this.setOutputData(0, this._value); + this.setOutputData(1, this._value ? ( this._value.length || 0) : 0 ); + }; + + ConstantArray.prototype.setValue = ConstantNumber.prototype.setValue; + + LiteGraph.registerNodeType("basic/array", ConstantArray); + + function ArrayElement() { + this.addInput("array", "array,table,string"); + this.addInput("index", "number"); + this.addOutput("value", ""); + this.addProperty("index",0); + } + + ArrayElement.title = "Array[i]"; + ArrayElement.desc = "Returns an element from an array"; + + ArrayElement.prototype.onExecute = function() { + var array = this.getInputData(0); + var index = this.getInputData(1); + if(index == null) + index = this.properties.index; + if(array == null || index == null ) + return; + this.setOutputData(0, array[Math.floor(Number(index))] ); + }; + + LiteGraph.registerNodeType("basic/array[]", ArrayElement); + + + + function TableElement() { + this.addInput("table", "table"); + this.addInput("row", "number"); + this.addInput("col", "number"); + this.addOutput("value", ""); + this.addProperty("row",0); + this.addProperty("column",0); + } + + TableElement.title = "Table[row][col]"; + TableElement.desc = "Returns an element from a table"; + + TableElement.prototype.onExecute = function() { + var table = this.getInputData(0); + var row = this.getInputData(1); + var col = this.getInputData(2); + if(row == null) + row = this.properties.row; + if(col == null) + col = this.properties.column; + if(table == null || row == null || col == null) + return; + var row = table[Math.floor(Number(row))]; + if(row) + this.setOutputData(0, row[Math.floor(Number(col))] ); + else + this.setOutputData(0, null ); + }; + + LiteGraph.registerNodeType("basic/table[][]", TableElement); + function ObjectProperty() { this.addInput("obj", ""); this.addOutput("", ""); this.addProperty("value", ""); - this.widget = this.addWidget( - "text", - "prop.", - "", - this.setValue.bind(this) - ); + this.widget = this.addWidget("text","prop.","",this.setValue.bind(this) ); this.widgets_up = true; this.size = [140, 30]; this._value = null; @@ -690,28 +1045,70 @@ this.size = [60, 30]; this.addInput("in"); this.addOutput("out"); - this.properties = { varname: "myname", global: false }; + this.properties = { varname: "myname", container: Variable.LITEGRAPH }; this.value = null; } Variable.title = "Variable"; Variable.desc = "store/read variable value"; + Variable.LITEGRAPH = 0; //between all graphs + Variable.GRAPH = 1; //only inside this graph + Variable.GLOBALSCOPE = 2; //attached to Window + + Variable["@container"] = { type: "enum", values: {"litegraph":Variable.LITEGRAPH, "graph":Variable.GRAPH,"global": Variable.GLOBALSCOPE} }; + Variable.prototype.onExecute = function() { - this.value = this.getInputData(0); - if(this.graph) - this.graph.vars[ this.properties.varname ] = this.value; - if(this.properties.global) - global[this.properties.varname] = this.value; - this.setOutputData(0, this.value ); + var container = this.getContainer(); + + if(this.isInputConnected(0)) + { + this.value = this.getInputData(0); + container[ this.properties.varname ] = this.value; + this.setOutputData(0, this.value ); + return; + } + + this.setOutputData( 0, container[ this.properties.varname ] ); }; + Variable.prototype.getContainer = function() + { + switch(this.properties.container) + { + case Variable.GRAPH: + if(this.graph) + return this.graph.vars; + return {}; + break; + case Variable.GLOBALSCOPE: + return global; + break; + case Variable.LITEGRAPH: + default: + return LiteGraph.Globals; + break; + } + } + Variable.prototype.getTitle = function() { return this.properties.varname; }; LiteGraph.registerNodeType("basic/variable", Variable); + function length(v) { + if(v && v.length != null) + return Number(v.length); + return 0; + } + + LiteGraph.wrapFunctionAsNode( + "basic/length", + length, + [""], + "number" + ); function DownloadData() { this.size = [60, 30]; diff --git a/src/nodes/events.js b/src/nodes/events.js index f091cff5f..943ce2b5a 100644 --- a/src/nodes/events.js +++ b/src/nodes/events.js @@ -20,22 +20,30 @@ //convert to Event if the value is true function TriggerEvent() { this.size = [60, 30]; - this.addInput("in", ""); + this.addInput("if", ""); this.addOutput("true", LiteGraph.EVENT); this.addOutput("change", LiteGraph.EVENT); - this.was_true = false; + this.addOutput("false", LiteGraph.EVENT); + this.properties = { only_on_change: true }; + this.prev = 0; } TriggerEvent.title = "TriggerEvent"; - TriggerEvent.desc = "Triggers event if value is true"; + TriggerEvent.desc = "Triggers event if input evaluates to true"; TriggerEvent.prototype.onExecute = function(action, param) { var v = this.getInputData(0); - if(v) + var changed = (v != this.prev); + if(this.prev === 0) + changed = false; + var must_resend = (changed && this.properties.only_on_change) || (!changed && !this.properties.only_on_change); + if(v && must_resend ) this.triggerSlot(0, param); - if(v && !this.was_true) + if(!v && must_resend) + this.triggerSlot(2, param); + if(changed) this.triggerSlot(1, param); - this.was_true = v; + this.prev = v; }; LiteGraph.registerNodeType("events/trigger", TriggerEvent); @@ -118,6 +126,29 @@ LiteGraph.registerNodeType("events/filter", FilterEvent); + + function EventBranch() { + this.addInput("in", LiteGraph.ACTION); + this.addInput("cond", "boolean"); + this.addOutput("true", LiteGraph.EVENT); + this.addOutput("false", LiteGraph.EVENT); + this.size = [120, 60]; + this._value = false; + } + + EventBranch.title = "Branch"; + EventBranch.desc = "If condition is true, outputs triggers true, otherwise false"; + + EventBranch.prototype.onExecute = function() { + this._value = this.getInputData(1); + } + + EventBranch.prototype.onAction = function(action, param) { + this.triggerSlot(this._value ? 0 : 1); + } + + LiteGraph.registerNodeType("events/branch", EventBranch); + //Show value inside the debug console function EventCounter() { this.addInput("inc", LiteGraph.ACTION); diff --git a/src/nodes/geometry.js b/src/nodes/geometry.js index bfaa02101..c09edc830 100644 --- a/src/nodes/geometry.js +++ b/src/nodes/geometry.js @@ -65,8 +65,9 @@ LGraphPoints3D.OBJECT = 20; LGraphPoints3D.OBJECT_UNIFORMLY = 21; + LGraphPoints3D.OBJECT_INSIDE = 22; - LGraphPoints3D.MODE_VALUES = { "rectangle":LGraphPoints3D.RECTANGLE, "circle":LGraphPoints3D.CIRCLE, "cube":LGraphPoints3D.CUBE, "sphere":LGraphPoints3D.SPHERE, "hemisphere":LGraphPoints3D.HEMISPHERE, "inside_sphere":LGraphPoints3D.INSIDE_SPHERE, "object":LGraphPoints3D.OBJECT, "object_uniformly":LGraphPoints3D.OBJECT_UNIFORMLY }; + LGraphPoints3D.MODE_VALUES = { "rectangle":LGraphPoints3D.RECTANGLE, "circle":LGraphPoints3D.CIRCLE, "cube":LGraphPoints3D.CUBE, "sphere":LGraphPoints3D.SPHERE, "hemisphere":LGraphPoints3D.HEMISPHERE, "inside_sphere":LGraphPoints3D.INSIDE_SPHERE, "object":LGraphPoints3D.OBJECT, "object_uniformly":LGraphPoints3D.OBJECT_UNIFORMLY, "object_inside":LGraphPoints3D.OBJECT_INSIDE }; LGraphPoints3D.widgets_info = { mode: { widget: "combo", values: LGraphPoints3D.MODE_VALUES } @@ -164,7 +165,7 @@ if(normals) { for(var i = 0; i < normals.length; i+=3) - normals.set(i, UP); + normals.set(UP, i); } } else if( mode == LGraphPoints3D.SPHERE) @@ -195,7 +196,7 @@ if(normals) { for(var i = 0; i < normals.length; i+=3) - normals.set(i, UP); + normals.set(UP, i); } } } @@ -212,7 +213,7 @@ if(normals) { for(var i = 0; i < normals.length; i+=3) - normals.set(i, UP); + normals.set(UP, i); } } else if( mode == LGraphPoints3D.CUBE) @@ -226,7 +227,7 @@ if(normals) { for(var i = 0; i < normals.length; i+=3) - normals.set(i, UP); + normals.set(UP, i); } } else if( mode == LGraphPoints3D.SPHERE) @@ -261,6 +262,12 @@ { LGraphPoints3D.generateFromObject( points, normals, size, obj, true ); } + else if( mode == LGraphPoints3D.OBJECT_INSIDE) + { + LGraphPoints3D.generateFromInsideObject( points, size, obj ); + //if(normals) + // LGraphPoints3D.generateSphericalNormals( points, normals ); + } else console.warn("wrong mode in LGraphPoints3D"); } @@ -466,6 +473,36 @@ } } + LGraphPoints3D.generateFromInsideObject = function( points, size, mesh ) + { + if(!mesh || mesh.constructor !== GL.Mesh) + return; + + var aabb = mesh.getBoundingBox(); + if(!mesh.octree) + mesh.octree = new GL.Octree( mesh ); + var octree = mesh.octree; + var origin = vec3.create(); + var direction = vec3.fromValues(1,0,0); + var temp = vec3.create(); + var i = 0; + var tries = 0; + while(i < size && tries < points.length * 10) //limit to avoid problems + { + tries += 1 + var r = vec3.random(temp); //random point inside the aabb + r[0] = (r[0] * 2 - 1) * aabb[3] + aabb[0]; + r[1] = (r[1] * 2 - 1) * aabb[4] + aabb[1]; + r[2] = (r[2] * 2 - 1) * aabb[5] + aabb[2]; + origin.set(r); + var hit = octree.testRay( origin, direction, 0, 10000, true, GL.Octree.ALL ); + if(!hit || hit.length % 2 == 0) //not inside + continue; + points.set( r, i ); + i+=3; + } + } + LiteGraph.registerNodeType( "geometry/points3D", LGraphPoints3D ); @@ -474,11 +511,13 @@ this.addInput("points", "geometry"); this.addOutput("instances", "[mat4]"); this.properties = { - mode: 1 + mode: 1, + autoupdate: true }; this.must_update = true; this.matrices = []; + this.first_time = true; } LGraphPointsToInstances.NORMAL = 0; @@ -506,8 +545,13 @@ if( !this.isOutputConnected(0) ) return; - if( geo._version != this._version || geo._id != this._geometry_id ) + var has_changed = (geo._version != this._version || geo._id != this._geometry_id); + + if( has_changed && this.properties.autoupdate || this.first_time ) + { + this.first_time = false; this.updateInstances( geo ); + } this.setOutputData( 0, this.matrices ); } @@ -611,7 +655,8 @@ this.geometry = { type: "triangles", vertices: null, - _id: generateGeometryId() + _id: generateGeometryId(), + _version: 0 }; this._last_geometry_id = -1; @@ -730,7 +775,7 @@ this.addInput("sides", "number"); this.addInput("radius", "number"); this.addOutput("out", "geometry"); - this.properties = { sides: 6, radius: 1 } + this.properties = { sides: 6, radius: 1, uvs: false } this.geometry = { type: "line_loop", @@ -768,6 +813,13 @@ if( !vertices || vertices.length != num ) vertices = this.geometry.vertices = new Float32Array( 3*sides ); var delta = (Math.PI * 2) / sides; + var gen_uvs = this.properties.uvs; + if(gen_uvs) + { + uvs = this.geometry.coords = new Float32Array( 3*sides ); + } + + for(var i = 0; i < sides; ++i) { var angle = delta * -i; @@ -777,6 +829,12 @@ vertices[i*3] = x; vertices[i*3+1] = y; vertices[i*3+2] = z; + + if(gen_uvs) + { + + + } } this.geometry._id = ++this.geometry_id; this.geometry._version = ++this.version; diff --git a/src/nodes/glfx.js b/src/nodes/glfx.js index 681623881..69b9ec3e5 100755 --- a/src/nodes/glfx.js +++ b/src/nodes/glfx.js @@ -1,5 +1,6 @@ (function(global) { var LiteGraph = global.LiteGraph; + var LGraphTexture = global.LGraphTexture; //Works with Litegl.js to create WebGL nodes if (typeof GL != "undefined") { diff --git a/src/nodes/glshaders.js b/src/nodes/glshaders.js new file mode 100644 index 000000000..a1d6aefe7 --- /dev/null +++ b/src/nodes/glshaders.js @@ -0,0 +1,1588 @@ +(function(global) { + + if (typeof GL == "undefined") + return; + + var LiteGraph = global.LiteGraph; + var LGraphCanvas = global.LGraphCanvas; + + var SHADERNODES_COLOR = "#345"; + + var LGShaders = LiteGraph.Shaders = {}; + + var GLSL_types = LGShaders.GLSL_types = ["float","vec2","vec3","vec4","mat3","mat4","sampler2D","samplerCube"]; + var GLSL_types_const = LGShaders.GLSL_types_const = ["float","vec2","vec3","vec4"]; + + var GLSL_functions_desc = { + "radians": "T radians(T degrees)", + "degrees": "T degrees(T radians)", + "sin": "T sin(T angle)", + "cos": "T cos(T angle)", + "tan": "T tan(T angle)", + "asin": "T asin(T x)", + "acos": "T acos(T x)", + "atan": "T atan(T x)", + "atan2": "T atan(T x,T y)", + "pow": "T pow(T x,T y)", + "exp": "T exp(T x)", + "log": "T log(T x)", + "exp2": "T exp2(T x)", + "log2": "T log2(T x)", + "sqrt": "T sqrt(T x)", + "inversesqrt": "T inversesqrt(T x)", + "abs": "T abs(T x)", + "sign": "T sign(T x)", + "floor": "T floor(T x)", + "ceil": "T ceil(T x)", + "fract": "T fract(T x)", + "mod": "T mod(T x,T y)", //"T mod(T x,float y)" + "min": "T min(T x,T y)", + "max": "T max(T x,T y)", + "clamp": "T clamp(T x,T minVal = 0.0,T maxVal = 1.0)", + "mix": "T mix(T x,T y,T a)", //"T mix(T x,T y,float a)" + "step": "T step(T edge, T edge2, T x)", //"T step(float edge, T x)" + "smoothstep": "T smoothstep(T edge, T edge2, T x)", //"T smoothstep(float edge, T x)" + "length":"float length(T x)", + "distance":"float distance(T p0, T p1)", + "normalize":"T normalize(T x)", + "dot": "float dot(T x,T y)", + "cross": "vec3 cross(vec3 x,vec3 y)" + }; + + //parse them + var GLSL_functions = {}; + var GLSL_functions_name = []; + parseGLSLDescriptions(); + + function parseGLSLDescriptions() + { + GLSL_functions_name.length = 0; + + for(var i in GLSL_functions_desc) + { + var op = GLSL_functions_desc[i]; + var index = op.indexOf(" "); + var return_type = op.substr(0,index); + var index2 = op.indexOf("(",index); + var func_name = op.substr(index,index2-index).trim(); + var params = op.substr(index2 + 1, op.length - index2 - 2).split(","); + for(var j in params) + { + var p = params[j].split(" ").filter(function(a){ return a; }); + params[j] = { type: p[0].trim(), name: p[1].trim() }; + if(p[2] == "=") + params[j].value = p[3].trim(); + } + GLSL_functions[i] = { return_type: return_type, func: func_name, params: params }; + GLSL_functions_name.push( func_name ); + //console.log( GLSL_functions[i] ); + } + } + + //common actions to all shader node classes + function registerShaderNode( type, node_ctor ) + { + //static attributes + node_ctor.color = SHADERNODES_COLOR; + node_ctor.filter = "shader"; + + //common methods + node_ctor.prototype.clearDestination = function(){ this.shader_destination = {}; } + node_ctor.prototype.propagateDestination = function propagateDestination( dest_name ) + { + this.shader_destination[ dest_name ] = true; + if(this.inputs) + for(var i = 0; i < this.inputs.length; ++i) + { + var origin_node = this.getInputNode(i); + if(origin_node) + origin_node.propagateDestination( dest_name ); + } + } + if(!node_ctor.prototype.onPropertyChanged) + node_ctor.prototype.onPropertyChanged = function() + { + this.graph._version++; + } + + LiteGraph.registerNodeType( "shader::" + type, node_ctor ); + } + + function getShaderNodeVarName( node, name ) + { + return "VAR_" + (name || "TEMP") + "_" + node.id; + } + + function getInputLinkID( node, slot ) + { + if(!node.inputs) + return null; + var link = node.getInputLink( slot ); + if( !link ) + return null; + var origin_node = node.graph.getNodeById( link.origin_id ); + if( !origin_node ) + return null; + if(origin_node.getOutputVarName) + return origin_node.getOutputVarName(link.origin_slot); + //generate + return "link_" + origin_node.id + "_" + link.origin_slot; + } + + function getOutputLinkID( node, slot ) + { + if (!node.isOutputConnected(slot)) + return null; + return "link_" + node.id + "_" + slot; + } + + LGShaders.registerShaderNode = registerShaderNode; + LGShaders.getInputLinkID = getInputLinkID; + LGShaders.getOutputLinkID = getOutputLinkID; + LGShaders.getShaderNodeVarName = getShaderNodeVarName; + LGShaders.parseGLSLDescriptions = parseGLSLDescriptions; + + //given a const number, it transform it to a string that matches a type + var valueToGLSL = LiteGraph.valueToGLSL = function valueToGLSL( v, type, precision ) + { + var n = 5; //num decimals + if(precision != null) + n = precision; + if(!type) + { + if(v.constructor === Number) + type = "float"; + else if(v.length) + { + switch(v.length) + { + case 2: type = "vec2"; break; + case 3: type = "vec3"; break; + case 4: type = "vec4"; break; + case 9: type = "mat3"; break; + case 16: type = "mat4"; break; + default: + throw("unknown type for glsl value size"); + } + } + else + throw("unknown type for glsl value: " + v.constructor); + } + switch(type) + { + case 'float': return v.toFixed(n); break; + case 'vec2': return "vec2(" + v[0].toFixed(n) + "," + v[1].toFixed(n) + ")"; break; + case 'color3': + case 'vec3': return "vec3(" + v[0].toFixed(n) + "," + v[1].toFixed(n) + "," + v[2].toFixed(n) + ")"; break; + case 'color4': + case 'vec4': return "vec4(" + v[0].toFixed(n) + "," + v[1].toFixed(n) + "," + v[2].toFixed(n) + "," + v[3].toFixed(n) + ")"; break; + case 'mat3': return "mat3(1.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,1.0)"; break; //not fully supported yet + case 'mat4': return "mat4(1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0)"; break;//not fully supported yet + default: + throw("unknown glsl type in valueToGLSL:", type); + } + + return ""; + } + + //makes sure that a var is of a type, and if not, it converts it + var varToTypeGLSL = LiteGraph.varToTypeGLSL = function varToTypeGLSL( v, input_type, output_type ) + { + if(input_type == output_type) + return v; + if(v == null) + switch(output_type) + { + case "float": return "0.0"; + case "vec2": return "vec2(0.0)"; + case "vec3": return "vec3(0.0)"; + case "vec4": return "vec4(0.0,0.0,0.0,1.0)"; + default: //null + return null; + } + + if(!output_type) + throw("error: no output type specified"); + if(output_type == "float") + { + switch(input_type) + { + //case "float": + case "vec2": + case "vec3": + case "vec4": + return v + ".x"; + break; + default: //null + return "0.0"; + break; + } + } + else if(output_type == "vec2") + { + switch(input_type) + { + case "float": + return "vec2("+v+")"; + //case "vec2": + case "vec3": + case "vec4": + return v + ".xy"; + default: //null + return "vec2(0.0)"; + } + } + else if(output_type == "vec3") + { + switch(input_type) + { + case "float": + return "vec3("+v+")"; + case "vec2": + return "vec3(" + v + ",0.0)"; + //case "vec3": + case "vec4": + return v + ".xyz"; + default: //null + return "vec3(0.0)"; + } + } + else if(output_type == "vec4") + { + switch(input_type) + { + case "float": + return "vec4("+v+")"; + case "vec2": + return "vec4(" + v + ",0.0,1.0)"; + case "vec3": + return "vec4(" + v + ",1.0)"; + default: //null + return "vec4(0.0,0.0,0.0,1.0)"; + } + } + throw("type cannot be converted"); + } + + + //used to plug incompatible stuff + var convertVarToGLSLType = LiteGraph.convertVarToGLSLType = function convertVarToGLSLType( varname, type, target_type ) + { + if(type == target_type) + return varname; + if(type == "float") + return target_type + "(" + varname + ")"; + if(target_type == "vec2") //works for vec2,vec3 and vec4 + return "vec2(" + varname + ".xy)"; + if(target_type == "vec3") //works for vec2,vec3 and vec4 + { + if(type == "vec2") + return "vec3(" + varname + ",0.0)"; + if(type == "vec4") + return "vec4(" + varname + ".xyz)"; + } + if(target_type == "vec4") + { + if(type == "vec2") + return "vec4(" + varname + ",0.0,0.0)"; + if(target_type == "vec3") + return "vec4(" + varname + ",1.0)"; + } + return null; + } + + //used to host a shader body ************************************** + function LGShaderContext() + { + //to store the code template + this.vs_template = ""; + this.fs_template = ""; + + //required so nodes now where to fetch the input data + this.buffer_names = { + uvs: "v_coord" + }; + + this.extra = {}; //to store custom info from the nodes (like if this shader supports a feature, etc) + + this._functions = {}; + this._uniforms = {}; + this._codeparts = {}; + this._uniform_value = null; + } + + LGShaderContext.prototype.clear = function() + { + this._uniforms = {}; + this._functions = {}; + this._codeparts = {}; + this._uniform_value = null; + + this.extra = {}; + } + + LGShaderContext.prototype.addUniform = function( name, type, value ) + { + this._uniforms[ name ] = type; + if(value != null) + { + if(!this._uniform_value) + this._uniform_value = {}; + this._uniform_value[name] = value; + } + } + + LGShaderContext.prototype.addFunction = function( name, code ) + { + this._functions[name] = code; + } + + LGShaderContext.prototype.addCode = function( hook, code, destinations ) + { + destinations = destinations || {"":""}; + for(var i in destinations) + { + var h = i ? i + "_" + hook : hook; + if(!this._codeparts[ h ]) + this._codeparts[ h ] = code + "\n"; + else + this._codeparts[ h ] += code + "\n"; + } + } + + //the system works by grabbing code fragments from every node and concatenating them in blocks depending on where must they be attached + LGShaderContext.prototype.computeCodeBlocks = function( graph, extra_uniforms ) + { + //prepare context + this.clear(); + + //grab output nodes + var vertexout = graph.findNodesByType("shader::output/vertex"); + vertexout = vertexout && vertexout.length ? vertexout[0] : null; + var fragmentout = graph.findNodesByType("shader::output/fragcolor"); + fragmentout = fragmentout && fragmentout.length ? fragmentout[0] : null; + if(!fragmentout) //?? + return null; + + //propagate back destinations + graph.sendEventToAllNodes( "clearDestination" ); + if(vertexout) + vertexout.propagateDestination("vs"); + if(fragmentout) + fragmentout.propagateDestination("fs"); + + //gets code from graph + graph.sendEventToAllNodes("onGetCode", this ); + + var uniforms = ""; + for(var i in this._uniforms) + uniforms += "uniform " + this._uniforms[i] + " " + i + ";\n"; + if(extra_uniforms) + for(var i in extra_uniforms) + uniforms += "uniform " + extra_uniforms[i] + " " + i + ";\n"; + + var functions = ""; + for(var i in this._functions) + functions += "//" + i + "\n" + this._functions[i] + "\n"; + + var blocks = this._codeparts; + blocks.uniforms = uniforms; + blocks.functions = functions; + return blocks; + } + + //replaces blocks using the vs and fs template and returns the final codes + LGShaderContext.prototype.computeShaderCode = function( graph ) + { + var blocks = this.computeCodeBlocks( graph ); + var vs_code = GL.Shader.replaceCodeUsingContext( this.vs_template, blocks ); + var fs_code = GL.Shader.replaceCodeUsingContext( this.fs_template, blocks ); + return { + vs_code: vs_code, + fs_code: fs_code + }; + } + + //generates the shader code from the template and the + LGShaderContext.prototype.computeShader = function( graph, shader ) + { + var finalcode = this.computeShaderCode( graph ); + console.log( finalcode.vs_code, finalcode.fs_code ); + + if(!LiteGraph.catch_exceptions) + { + this._shader_error = true; + if(shader) + shader.updateShader( finalcode.vs_code, finalcode.fs_code ); + else + shader = new GL.Shader( finalcode.vs_code, finalcode.fs_code ); + this._shader_error = false; + return shader; + } + + try + { + if(shader) + shader.updateShader( finalcode.vs_code, finalcode.fs_code ); + else + shader = new GL.Shader( finalcode.vs_code, finalcode.fs_code ); + this._shader_error = false; + return shader; + } + catch (err) + { + if(!this._shader_error) + { + console.error(err); + if(err.indexOf("Fragment shader") != -1) + console.log( finalcode.fs_code.split("\n").map(function(v,i){ return i + ".- " + v; }).join("\n") ); + else + console.log( finalcode.vs_code ); + } + this._shader_error = true; + return null; + } + + return null;//never here + } + + LGShaderContext.prototype.getShader = function( graph ) + { + //if graph not changed? + if(this._shader && this._shader._version == graph._version) + return this._shader; + + //compile shader + var shader = this.computeShader( graph, this._shader ); + if(!shader) + return null; + + this._shader = shader; + shader._version = graph._version; + return shader; + } + + //some shader nodes could require to fill the box with some uniforms + LGShaderContext.prototype.fillUniforms = function( uniforms, param ) + { + if(!this._uniform_value) + return; + + for(var i in this._uniform_value) + { + var v = this._uniform_value[i]; + if(v == null) + continue; + if(v.constructor === Function) + uniforms[i] = v.call( this, param ); + else if(v.constructor === GL.Texture) + { + //todo... + } + else + uniforms[i] = v; + } + } + + LiteGraph.ShaderContext = LiteGraph.Shaders.Context = LGShaderContext; + + // LGraphShaderGraph ***************************** + // applies a shader graph to texture, it can be uses as an example + + function LGraphShaderGraph() { + + //before inputs + this.subgraph = new LiteGraph.LGraph(); + this.subgraph._subgraph_node = this; + this.subgraph._is_subgraph = true; + this.subgraph.filter = "shader"; + + this.addInput("in", "texture"); + this.addOutput("out", "texture"); + this.properties = { width: 0, height: 0, alpha: false, precision: typeof(LGraphTexture) != "undefined" ? LGraphTexture.DEFAULT : 2 }; + + var inputNode = this.subgraph.findNodesByType("shader::input/uniform")[0]; + inputNode.pos = [200,300]; + + var sampler = LiteGraph.createNode("shader::texture/sampler2D"); + sampler.pos = [400,300]; + this.subgraph.add( sampler ); + + var outnode = LiteGraph.createNode("shader::output/fragcolor"); + outnode.pos = [600,300]; + this.subgraph.add( outnode ); + + inputNode.connect( 0, sampler ); + sampler.connect( 0, outnode ); + + this.size = [180,60]; + this.redraw_on_mouse = true; //force redraw + + this._uniforms = {}; + this._shader = null; + this._context = new LGShaderContext(); + this._context.vs_template = "#define VERTEX\n" + GL.Shader.SCREEN_VERTEX_SHADER; + this._context.fs_template = LGraphShaderGraph.template; + } + + LGraphShaderGraph.template = "\n\ +#define FRAGMENT\n\ +precision highp float;\n\ +varying vec2 v_coord;\n\ +{{varying}}\n\ +{{uniforms}}\n\ +{{functions}}\n\ +{{fs_functions}}\n\ +void main() {\n\n\ +vec2 uv = v_coord;\n\ +vec4 fragcolor = vec4(0.0);\n\ +vec4 fragcolor1 = vec4(0.0);\n\ +{{fs_code}}\n\ +gl_FragColor = fragcolor;\n\ +}\n\ + "; + + LGraphShaderGraph.widgets_info = { + precision: { widget: "combo", values: LGraphTexture.MODE_VALUES } + }; + + LGraphShaderGraph.title = "ShaderGraph"; + LGraphShaderGraph.desc = "Builds a shader using a graph"; + LGraphShaderGraph.input_node_type = "input/uniform"; + LGraphShaderGraph.output_node_type = "output/fragcolor"; + LGraphShaderGraph.title_color = SHADERNODES_COLOR; + + LGraphShaderGraph.prototype.onSerialize = function(o) + { + o.subgraph = this.subgraph.serialize(); + } + + LGraphShaderGraph.prototype.onConfigure = function(o) + { + this.subgraph.configure(o.subgraph); + } + + LGraphShaderGraph.prototype.onExecute = function() { + if (!this.isOutputConnected(0)) + return; + + //read input texture + var intex = this.getInputData(0); + if(intex && intex.constructor != GL.Texture) + intex = null; + + var w = this.properties.width | 0; + var h = this.properties.height | 0; + if (w == 0) { + w = intex ? intex.width : gl.viewport_data[2]; + } //0 means default + if (h == 0) { + h = intex ? intex.height : gl.viewport_data[3]; + } //0 means default + + var type = LGraphTexture.getTextureType( this.properties.precision, intex ); + + var texture = this._texture; + if ( !texture || texture.width != w || texture.height != h || texture.type != type ) { + texture = this._texture = new GL.Texture(w, h, { + type: type, + format: this.alpha ? gl.RGBA : gl.RGB, + filter: gl.LINEAR + }); + } + + var shader = this.getShader( this.subgraph ); + if(!shader) + return; + + var uniforms = this._uniforms; + this._context.fillUniforms( uniforms ); + + var tex_slot = 0; + if(this.inputs) + for(var i = 0; i < this.inputs.length; ++i) + { + var input = this.inputs[i]; + var data = this.getInputData(i); + if(input.type == "texture") + { + if(!data) + data = GL.Texture.getWhiteTexture(); + data = data.bind(tex_slot++); + } + + if(data != null) + uniforms[ "u_" + input.name ] = data; + } + + var mesh = GL.Mesh.getScreenQuad(); + + gl.disable( gl.DEPTH_TEST ); + gl.disable( gl.BLEND ); + + texture.drawTo(function(){ + shader.uniforms( uniforms ); + shader.draw( mesh ); + }); + + //use subgraph output + this.setOutputData(0, texture ); + }; + + //add input node inside subgraph + LGraphShaderGraph.prototype.onInputAdded = function( slot_info ) + { + var subnode = LiteGraph.createNode("shader::input/uniform"); + subnode.setProperty("name",slot_info.name); + subnode.setProperty("type",slot_info.type); + this.subgraph.add( subnode ); + } + + //remove all + LGraphShaderGraph.prototype.onInputRemoved = function( slot, slot_info ) + { + var nodes = this.subgraph.findNodesByType("shader::input/uniform"); + for(var i = 0; i < nodes.length; ++i) + { + var node = nodes[i]; + if(node.properties.name == slot_info.name ) + this.subgraph.remove( node ); + } + } + + LGraphShaderGraph.prototype.computeSize = function() + { + var num_inputs = this.inputs ? this.inputs.length : 0; + var num_outputs = this.outputs ? this.outputs.length : 0; + return [ 200, Math.max(num_inputs,num_outputs) * LiteGraph.NODE_SLOT_HEIGHT + LiteGraph.NODE_TITLE_HEIGHT + 10]; + } + + LGraphShaderGraph.prototype.getShader = function() + { + var shader = this._context.getShader( this.subgraph ); + if(!shader) + this.boxcolor = "red"; + else + this.boxcolor = null; + return shader; + } + + LGraphShaderGraph.prototype.onDrawBackground = function(ctx, graphcanvas, canvas, pos) + { + if(this.flags.collapsed) + return; + + //allows to preview the node if the canvas is a webgl canvas + var tex = this.getOutputData(0); + var inputs_y = this.inputs ? this.inputs.length * LiteGraph.NODE_SLOT_HEIGHT : 0; + if (tex && ctx == tex.gl && this.size[1] > inputs_y + LiteGraph.NODE_TITLE_HEIGHT ) { + ctx.drawImage( tex, 10,y, this.size[0] - 20, this.size[1] - inputs_y - LiteGraph.NODE_TITLE_HEIGHT ); + } + + var y = this.size[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5; + + //button + var over = LiteGraph.isInsideRectangle(pos[0],pos[1],this.pos[0],this.pos[1] + y,this.size[0],LiteGraph.NODE_TITLE_HEIGHT); + ctx.fillStyle = over ? "#555" : "#222"; + ctx.beginPath(); + if (this._shape == LiteGraph.BOX_SHAPE) + ctx.rect(0, y, this.size[0]+1, LiteGraph.NODE_TITLE_HEIGHT); + else + ctx.roundRect( 0, y, this.size[0]+1, LiteGraph.NODE_TITLE_HEIGHT, 0, 8); + ctx.fill(); + + //button + ctx.textAlign = "center"; + ctx.font = "24px Arial"; + ctx.fillStyle = over ? "#DDD" : "#999"; + ctx.fillText( "+", this.size[0] * 0.5, y + 24 ); + } + + LGraphShaderGraph.prototype.onMouseDown = function(e, localpos, graphcanvas) + { + var y = this.size[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5; + if(localpos[1] > y) + { + graphcanvas.showSubgraphPropertiesDialog(this); + } + } + + LGraphShaderGraph.prototype.onDrawSubgraphBackground = function(graphcanvas) + { + //TODO + } + + LGraphShaderGraph.prototype.getExtraMenuOptions = function(graphcanvas) + { + var that = this; + var options = [{ content: "Print Code", callback: function(){ + var code = that._context.computeShaderCode(); + console.log( code.vs_code, code.fs_code ); + }}]; + + return options; + } + + LiteGraph.registerNodeType( "texture/shaderGraph", LGraphShaderGraph ); + + function shaderNodeFromFunction( classname, params, return_type, code ) + { + //TODO + } + + //Shader Nodes *********************************************************** + + //applies a shader graph to a code + function LGraphShaderUniform() { + this.addOutput("out", ""); + this.properties = { name: "", type: "" }; + } + + LGraphShaderUniform.title = "Uniform"; + LGraphShaderUniform.desc = "Input data for the shader"; + + LGraphShaderUniform.prototype.getTitle = function() + { + if( this.properties.name && this.flags.collapsed) + return this.properties.type + " " + this.properties.name; + return "Uniform"; + } + + LGraphShaderUniform.prototype.onPropertyChanged = function(name,value) + { + this.outputs[0].name = this.properties.type + " " + this.properties.name; + } + + LGraphShaderUniform.prototype.onGetCode = function( context ) + { + var type = this.properties.type; + if( !type ) + return; + if(type == "number") + type = "float"; + else if(type == "texture") + type = "sampler2D"; + if ( LGShaders.GLSL_types.indexOf(type) == -1 ) + return; + + context.addUniform( "u_" + this.properties.name, type ); + this.setOutputData( 0, type ); + } + + LGraphShaderUniform.prototype.getOutputVarName = function(slot) + { + return "u_" + this.properties.name; + } + + registerShaderNode( "input/uniform", LGraphShaderUniform ); + + + function LGraphShaderAttribute() { + this.addOutput("out", "vec2"); + this.properties = { name: "coord", type: "vec2" }; + } + + LGraphShaderAttribute.title = "Attribute"; + LGraphShaderAttribute.desc = "Input data from mesh attribute"; + + LGraphShaderAttribute.prototype.getTitle = function() + { + return "att. " + this.properties.name; + } + + LGraphShaderAttribute.prototype.onGetCode = function( context ) + { + var type = this.properties.type; + if( !type || LGShaders.GLSL_types.indexOf(type) == -1 ) + return; + if(type == "number") + type = "float"; + if( this.properties.name != "coord") + { + context.addCode( "varying", " varying " + type +" v_" + this.properties.name + ";" ); + //if( !context.varyings[ this.properties.name ] ) + //context.addCode( "vs_code", "v_" + this.properties.name + " = " + input_name + ";" ); + } + this.setOutputData( 0, type ); + } + + LGraphShaderAttribute.prototype.getOutputVarName = function(slot) + { + return "v_" + this.properties.name; + } + + registerShaderNode( "input/attribute", LGraphShaderAttribute ); + + function LGraphShaderSampler2D() { + this.addInput("tex", "sampler2D"); + this.addInput("uv", "vec2"); + this.addOutput("rgba", "vec4"); + this.addOutput("rgb", "vec3"); + } + + LGraphShaderSampler2D.title = "Sampler2D"; + LGraphShaderSampler2D.desc = "Reads a pixel from a texture"; + + LGraphShaderSampler2D.prototype.onGetCode = function( context ) + { + var texname = getInputLinkID( this, 0 ); + var varname = getShaderNodeVarName(this); + var code = "vec4 " + varname + " = vec4(0.0);\n"; + if(texname) + { + var uvname = getInputLinkID( this, 1 ) || context.buffer_names.uvs; + code += varname + " = texture2D("+texname+","+uvname+");\n"; + } + + var link0 = getOutputLinkID( this, 0 ); + if(link0) + code += "vec4 " + getOutputLinkID( this, 0 ) + " = "+varname+";\n"; + + var link1 = getOutputLinkID( this, 1 ); + if(link1) + code += "vec3 " + getOutputLinkID( this, 1 ) + " = "+varname+".xyz;\n"; + + context.addCode( "code", code, this.shader_destination ); + this.setOutputData( 0, "vec4" ); + this.setOutputData( 1, "vec3" ); + } + + registerShaderNode( "texture/sampler2D", LGraphShaderSampler2D ); + + //********************************* + + function LGraphShaderConstant() + { + this.addOutput("","float"); + + this.properties = { + type: "float", + value: 0 + }; + + this.addWidget("combo","type","float",null, { values: GLSL_types_const, property: "type" } ); + this.updateWidgets(); + } + + LGraphShaderConstant.title = "const"; + + LGraphShaderConstant.prototype.getTitle = function() + { + if(this.flags.collapsed) + return valueToGLSL( this.properties.value, this.properties.type, 2 ); + return "Const"; + } + + LGraphShaderConstant.prototype.onPropertyChanged = function(name,value) + { + var that = this; + if(name == "type") + { + if(this.outputs[0].type != value) + { + this.disconnectOutput(0); + this.outputs[0].type = value; + } + this.widgets.length = 1; //remove extra widgets + this.updateWidgets(); + } + if(name == "value") + { + if(!value.length) + this.widgets[1].value = value; + else + { + this.widgets[1].value = value[1]; + if(value.length > 2) + this.widgets[2].value = value[2]; + if(value.length > 3) + this.widgets[3].value = value[3]; + } + } + } + + LGraphShaderConstant.prototype.updateWidgets = function( old_value ) + { + var that = this; + var old_value = this.properties.value; + var options = { step: 0.01 }; + switch(this.properties.type) + { + case 'float': + this.properties.value = 0; + this.addWidget("number","v",0,{ step:0.01, property: "value" }); + break; + case 'vec2': + this.properties.value = old_value && old_value.length == 2 ? [old_value[0],old_value[1]] : [0,0,0]; + this.addWidget("number","x",this.properties.value[0], function(v){ that.properties.value[0] = v; },options); + this.addWidget("number","y",this.properties.value[1], function(v){ that.properties.value[1] = v; },options); + break; + case 'vec3': + this.properties.value = old_value && old_value.length == 3 ? [old_value[0],old_value[1],old_value[2]] : [0,0,0]; + this.addWidget("number","x",this.properties.value[0], function(v){ that.properties.value[0] = v; },options); + this.addWidget("number","y",this.properties.value[1], function(v){ that.properties.value[1] = v; },options); + this.addWidget("number","z",this.properties.value[2], function(v){ that.properties.value[2] = v; },options); + break; + case 'vec4': + this.properties.value = old_value && old_value.length == 4 ? [old_value[0],old_value[1],old_value[2],old_value[3]] : [0,0,0,0]; + this.addWidget("number","x",this.properties.value[0], function(v){ that.properties.value[0] = v; },options); + this.addWidget("number","y",this.properties.value[1], function(v){ that.properties.value[1] = v; },options); + this.addWidget("number","z",this.properties.value[2], function(v){ that.properties.value[2] = v; },options); + this.addWidget("number","w",this.properties.value[3], function(v){ that.properties.value[3] = v; },options); + break; + default: + console.error("unknown type for constant"); + } + } + + LGraphShaderConstant.prototype.onGetCode = function( context ) + { + var value = valueToGLSL( this.properties.value, this.properties.type ); + var link_name = getOutputLinkID(this,0); + if(!link_name) //not connected + return; + + var code = " " + this.properties.type + " " + link_name + " = " + value + ";"; + context.addCode( "code", code, this.shader_destination ); + + this.setOutputData( 0, this.properties.type ); + } + + registerShaderNode( "const/const", LGraphShaderConstant ); + + function LGraphShaderVec2() + { + this.addInput("xy","vec2"); + this.addInput("x","float"); + this.addInput("y","float"); + this.addOutput("xy","vec2"); + this.addOutput("x","float"); + this.addOutput("y","float"); + + this.properties = { x: 0, y: 0 }; + } + + LGraphShaderVec2.title = "vec2"; + LGraphShaderVec2.varmodes = ["xy","x","y"]; + + LGraphShaderVec2.prototype.onPropertyChanged = function() + { + this.graph._version++; + } + + LGraphShaderVec2.prototype.onGetCode = function( context ) + { + var props = this.properties; + + var varname = getShaderNodeVarName(this); + var code = " vec2 " + varname + " = " + valueToGLSL([props.x,props.y]) + ";\n"; + + for(var i = 0;i < LGraphShaderVec2.varmodes.length; ++i) + { + var varmode = LGraphShaderVec2.varmodes[i]; + var inlink = getInputLinkID(this,i); + if(!inlink) + continue; + code += " " + varname + "."+varmode+" = " + inlink + ";\n"; + } + + for(var i = 0;i < LGraphShaderVec2.varmodes.length; ++i) + { + var varmode = LGraphShaderVec2.varmodes[i]; + var outlink = getOutputLinkID(this,i); + if(!outlink) + continue; + var type = GLSL_types_const[varmode.length - 1]; + code += " "+type+" " + outlink + " = " + varname + "." + varmode + ";\n"; + this.setOutputData( i, type ); + } + + context.addCode( "code", code, this.shader_destination ); + } + + registerShaderNode( "const/vec2", LGraphShaderVec2 ); + + function LGraphShaderVec3() + { + this.addInput("xyz","vec3"); + this.addInput("x","float"); + this.addInput("y","float"); + this.addInput("z","float"); + this.addInput("xy","vec2"); + this.addInput("xz","vec2"); + this.addInput("yz","vec2"); + this.addOutput("xyz","vec3"); + this.addOutput("x","float"); + this.addOutput("y","float"); + this.addOutput("z","float"); + this.addOutput("xy","vec2"); + this.addOutput("xz","vec2"); + this.addOutput("yz","vec2"); + + this.properties = { x:0, y: 0, z: 0 }; + } + + LGraphShaderVec3.title = "vec3"; + LGraphShaderVec3.varmodes = ["xyz","x","y","z","xy","xz","yz"]; + + LGraphShaderVec3.prototype.onPropertyChanged = function() + { + this.graph._version++; + } + + + LGraphShaderVec3.prototype.onPropertyChanged = function() + { + this.graph._version++; + } + + LGraphShaderVec3.prototype.onGetCode = function( context ) + { + var props = this.properties; + + var varname = getShaderNodeVarName(this); + var code = "vec3 " + varname + " = " + valueToGLSL([props.x,props.y,props.z]) + ";\n"; + + for(var i = 0;i < LGraphShaderVec3.varmodes.length; ++i) + { + var varmode = LGraphShaderVec3.varmodes[i]; + var inlink = getInputLinkID(this,i); + if(!inlink) + continue; + code += " " + varname + "."+varmode+" = " + inlink + ";\n"; + } + + for(var i = 0; i < LGraphShaderVec3.varmodes.length; ++i) + { + var varmode = LGraphShaderVec3.varmodes[i]; + var outlink = getOutputLinkID(this,i); + if(!outlink) + continue; + var type = GLSL_types_const[varmode.length - 1]; + code += " "+type+" " + outlink + " = " + varname + "." + varmode + ";\n"; + this.setOutputData( i, type ); + } + + context.addCode( "code", code, this.shader_destination ); + } + + registerShaderNode( "const/vec3", LGraphShaderVec3 ); + + + function LGraphShaderVec4() + { + this.addInput("xyzw","vec4"); + this.addInput("xyz","vec3"); + this.addInput("x","float"); + this.addInput("y","float"); + this.addInput("z","float"); + this.addInput("w","float"); + this.addInput("xy","vec2"); + this.addInput("yz","vec2"); + this.addInput("zw","vec2"); + this.addOutput("xyzw","vec4"); + this.addOutput("xyz","vec3"); + this.addOutput("x","float"); + this.addOutput("y","float"); + this.addOutput("z","float"); + this.addOutput("xy","vec2"); + this.addOutput("yz","vec2"); + this.addOutput("zw","vec2"); + + this.properties = { x:0, y: 0, z: 0, w: 0 }; + } + + LGraphShaderVec4.title = "vec4"; + LGraphShaderVec4.varmodes = ["xyzw","xyz","x","y","z","w","xy","yz","zw"]; + + LGraphShaderVec4.prototype.onPropertyChanged = function() + { + this.graph._version++; + } + + LGraphShaderVec4.prototype.onGetCode = function( context ) + { + var props = this.properties; + + var varname = getShaderNodeVarName(this); + var code = "vec4 " + varname + " = " + valueToGLSL([props.x,props.y,props.z,props.w]) + ";\n"; + + for(var i = 0;i < LGraphShaderVec4.varmodes.length; ++i) + { + var varmode = LGraphShaderVec4.varmodes[i]; + var inlink = getInputLinkID(this,i); + if(!inlink) + continue; + code += " " + varname + "."+varmode+" = " + inlink + ";\n"; + } + + for(var i = 0;i < LGraphShaderVec4.varmodes.length; ++i) + { + var varmode = LGraphShaderVec4.varmodes[i]; + var outlink = getOutputLinkID(this,i); + if(!outlink) + continue; + var type = GLSL_types_const[varmode.length - 1]; + code += " "+type+" " + outlink + " = " + varname + "." + varmode + ";\n"; + this.setOutputData( i, type ); + } + + context.addCode( "code", code, this.shader_destination ); + + } + + registerShaderNode( "const/vec4", LGraphShaderVec4 ); + + //********************************* + + function LGraphShaderFragColor() { + this.addInput("color", "float,vec2,vec3,vec4"); + this.block_delete = true; + } + + LGraphShaderFragColor.title = "FragColor"; + LGraphShaderFragColor.desc = "Pixel final color"; + + LGraphShaderFragColor.prototype.onGetCode = function( context ) + { + var link_name = getInputLinkID( this, 0 ); + if(!link_name) + return; + var type = this.getInputData(0); + var code = varToTypeGLSL( link_name, type, "vec4" ); + context.addCode("fs_code", "fragcolor = " + code + ";"); + } + + registerShaderNode( "output/fragcolor", LGraphShaderFragColor ); + + + /* + function LGraphShaderDiscard() + { + this.addInput("v","T"); + this.addInput("min","T"); + this.properties = { min_value: 0.0 }; + this.addWidget("number","min",0,{ step: 0.01, property: "min_value" }); + } + + LGraphShaderDiscard.title = "Discard"; + + LGraphShaderDiscard.prototype.onGetCode = function( context ) + { + if(!this.isOutputConnected(0)) + return; + + var inlink = getInputLinkID(this,0); + var inlink1 = getInputLinkID(this,1); + + if(!inlink && !inlink1) //not connected + return; + context.addCode("code", return_type + " " + outlink + " = ( (" + inlink + " - "+minv+") / ("+ maxv+" - "+minv+") ) * ("+ maxv2+" - "+minv2+") + " + minv2 + ";", this.shader_destination ); + this.setOutputData( 0, return_type ); + } + + registerShaderNode( "output/discard", LGraphShaderDiscard ); + */ + + + // ************************************************* + + function LGraphShaderOperation() + { + this.addInput("A","float,vec2,vec3,vec4"); + this.addInput("B","float,vec2,vec3,vec4"); + this.addOutput("out",""); + this.properties = { + operation: "*" + }; + this.addWidget("combo","op.",this.properties.operation,{ property: "operation", values: LGraphShaderOperation.operations }); + } + + LGraphShaderOperation.title = "Operation"; + LGraphShaderOperation.operations = ["+","-","*","/"]; + + LGraphShaderOperation.prototype.getTitle = function() + { + if(this.flags.collapsed) + return "A" + this.properties.operation + "B"; + else + return "Operation"; + } + + LGraphShaderOperation.prototype.onGetCode = function( context ) + { + if(!this.isOutputConnected(0)) + return; + + var inlinks = []; + for(var i = 0; i < 3; ++i) + inlinks.push( { name: getInputLinkID(this,i), type: this.getInputData(i) || "float" } ); + + var outlink = getOutputLinkID(this,0); + if(!outlink) //not connected + return; + + //func_desc + var base_type = inlinks[0].type; + var return_type = base_type; + var op = this.properties.operation; + + var params = []; + for(var i = 0; i < 2; ++i) + { + var param_code = inlinks[i].name; + if(param_code == null) //not plugged + { + param_code = p.value != null ? p.value : "(1.0)"; + inlinks[i].type = "float"; + } + + //convert + if( inlinks[i].type != base_type ) + { + if( inlinks[i].type == "float" && (op == "*" || op == "/") ) + { + //I find hard to create the opposite condition now, so I prefeer an else + } + else + param_code = convertVarToGLSLType( param_code, inlinks[i].type, base_type ); + } + params.push( param_code ); + } + + context.addCode("code", return_type + " " + outlink + " = "+ params[0] + op + params[1] + ";", this.shader_destination ); + this.setOutputData( 0, return_type ); + } + + registerShaderNode( "math/operation", LGraphShaderOperation ); + + + function LGraphShaderFunc() + { + this.addInput("A","float,vec2,vec3,vec4"); + this.addInput("B","float,vec2,vec3,vec4"); + this.addOutput("out",""); + this.properties = { + func: "floor" + }; + this._current = "floor"; + this.addWidget("combo","func",this.properties.func,{ property: "func", values: GLSL_functions_name }); + } + + LGraphShaderFunc.title = "Func"; + + LGraphShaderFunc.prototype.onPropertyChanged = function(name,value) + { + this.graph._version++; + + if(name == "func") + { + var func_desc = GLSL_functions[ value ]; + if(!func_desc) + return; + + //remove extra inputs + for(var i = func_desc.params.length; i < this.inputs.length; ++i) + this.removeInput(i); + + //add and update inputs + for(var i = 0; i < func_desc.params.length; ++i) + { + var p = func_desc.params[i]; + if( this.inputs[i] ) + this.inputs[i].name = p.name + (p.value ? " (" + p.value + ")" : ""); + else + this.addInput( p.name, "float,vec2,vec3,vec4" ); + } + } + } + + LGraphShaderFunc.prototype.getTitle = function() + { + if(this.flags.collapsed) + return this.properties.func; + else + return "Func"; + } + + LGraphShaderFunc.prototype.onGetCode = function( context ) + { + if(!this.isOutputConnected(0)) + return; + + var inlinks = []; + for(var i = 0; i < 3; ++i) + inlinks.push( { name: getInputLinkID(this,i), type: this.getInputData(i) || "float" } ); + + var outlink = getOutputLinkID(this,0); + if(!outlink) //not connected + return; + + var func_desc = GLSL_functions[ this.properties.func ]; + if(!func_desc) + return; + + //func_desc + var base_type = inlinks[0].type; + var return_type = func_desc.return_type; + if( return_type == "T" ) + return_type = base_type; + + var params = []; + for(var i = 0; i < func_desc.params.length; ++i) + { + var p = func_desc.params[i]; + var param_code = inlinks[i].name; + if(param_code == null) //not plugged + { + param_code = p.value != null ? p.value : "(1.0)"; + inlinks[i].type = "float"; + } + if( (p.type == "T" && inlinks[i].type != base_type) || + (p.type != "T" && inlinks[i].type != base_type) ) + param_code = convertVarToGLSLType( param_code, inlinks[i].type, base_type ); + params.push( param_code ); + } + + context.addCode("code", return_type + " " + outlink + " = "+func_desc.func+"("+params.join(",")+");", this.shader_destination ); + + this.setOutputData( 0, return_type ); + } + + registerShaderNode( "math/func", LGraphShaderFunc ); + + + + function LGraphShaderSnippet() + { + this.addInput("A","float,vec2,vec3,vec4"); + this.addInput("B","float,vec2,vec3,vec4"); + this.addOutput("C","vec4"); + this.properties = { + code:"C = A+B", + type: "vec4" + } + this.addWidget("text","code",this.properties.code,{ property: "code" }); + this.addWidget("combo","type",this.properties.type,{ values:["float","vec2","vec3","vec4"], property: "type" }); + } + + LGraphShaderSnippet.title = "Snippet"; + + LGraphShaderSnippet.prototype.onPropertyChanged = function(name,value) + { + this.graph._version++; + + if(name == "type"&& this.outputs[0].type != value) + { + this.disconnectOutput(0); + this.outputs[0].type = value; + } + } + + LGraphShaderSnippet.prototype.getTitle = function() + { + if(this.flags.collapsed) + return this.properties.code; + else + return "Snippet"; + } + + LGraphShaderSnippet.prototype.onGetCode = function( context ) + { + if(!this.isOutputConnected(0)) + return; + + var inlinkA = getInputLinkID(this,0); + if(!inlinkA) + inlinkA = "1.0"; + var inlinkB = getInputLinkID(this,1); + if(!inlinkB) + inlinkB = "1.0"; + var outlink = getOutputLinkID(this,0); + if(!outlink) //not connected + return; + + var inA_type = this.getInputData(0) || "float"; + var inB_type = this.getInputData(1) || "float"; + var return_type = this.properties.type; + + //cannot resolve input + if(inA_type == "T" || inB_type == "T") + { + return null; + } + + var funcname = "funcSnippet" + this.id; + + var func_code = "\n" + return_type + " " + funcname + "( " + inA_type + " A, " + inB_type + " B) {\n"; + func_code += " " + return_type + " C = " + return_type + "(0.0);\n"; + func_code += " " + this.properties.code + ";\n"; + func_code += " return C;\n}\n"; + + context.addCode("functions", func_code, this.shader_destination ); + context.addCode("code", return_type + " " + outlink + " = "+funcname+"("+inlinkA+","+inlinkB+");", this.shader_destination ); + + this.setOutputData( 0, return_type ); + } + + registerShaderNode( "utils/snippet", LGraphShaderSnippet ); + + //************************************ + + function LGraphShaderRand() + { + this.addOutput("out","float"); + } + + LGraphShaderRand.title = "Rand"; + + LGraphShaderRand.prototype.onGetCode = function( context ) + { + if(!this.isOutputConnected(0)) + return; + + var outlink = getOutputLinkID(this,0); + + context.addUniform( "u_rand" + this.id, "float", function(){ return Math.random(); }); + context.addCode("code", "float " + outlink + " = u_rand" + this.id +";", this.shader_destination ); + this.setOutputData( 0, "float" ); + } + + registerShaderNode( "input/rand", LGraphShaderRand ); + + //noise? + //https://gist.github.com/patriciogonzalezvivo/670c22f3966e662d2f83 + + + function LGraphShaderTime() + { + this.addOutput("out","float"); + } + + LGraphShaderTime.title = "Time"; + + LGraphShaderTime.prototype.onGetCode = function( context ) + { + if(!this.isOutputConnected(0)) + return; + + var outlink = getOutputLinkID(this,0); + + context.addUniform( "u_time" + this.id, "float", function(){ return getTime() * 0.001; }); + context.addCode("code", "float " + outlink + " = u_time" + this.id +";", this.shader_destination ); + this.setOutputData( 0, "float" ); + } + + registerShaderNode( "input/time", LGraphShaderTime ); + + + function LGraphShaderDither() + { + this.addInput("in","T"); + this.addOutput("out","float"); + } + + LGraphShaderDither.title = "Dither"; + + LGraphShaderDither.prototype.onGetCode = function( context ) + { + if(!this.isOutputConnected(0)) + return; + var inlink = getInputLinkID(this,0); + var return_type = "float"; + var outlink = getOutputLinkID(this,0); + var intype = this.getInputData(0); + inlink = varToTypeGLSL( inlink, intype, "float" ); + context.addFunction("dither8x8", LGraphShaderDither.dither_func); + context.addCode("code", return_type + " " + outlink + " = dither8x8("+ inlink +");", this.shader_destination ); + this.setOutputData( 0, return_type ); + } + + LGraphShaderDither.dither_values = [0.515625,0.140625,0.640625,0.046875,0.546875,0.171875,0.671875,0.765625,0.265625,0.890625,0.390625,0.796875,0.296875,0.921875,0.421875,0.203125,0.703125,0.078125,0.578125,0.234375,0.734375,0.109375,0.609375,0.953125,0.453125,0.828125,0.328125,0.984375,0.484375,0.859375,0.359375,0.0625,0.5625,0.1875,0.6875,0.03125,0.53125,0.15625,0.65625,0.8125,0.3125,0.9375,0.4375,0.78125,0.28125,0.90625,0.40625,0.25,0.75,0.125,0.625,0.21875,0.71875,0.09375,0.59375,1.0001,0.5,0.875,0.375,0.96875,0.46875,0.84375,0.34375]; + + LGraphShaderDither.dither_func = "\n\ + float dither8x8(float brightness) {\n\ + vec2 position = vec2(0.0);\n\ + #ifdef FRAGMENT\n\ + position = gl_FragCoord.xy;\n\ + #endif\n\ + int x = int(mod(position.x, 8.0));\n\ + int y = int(mod(position.y, 8.0));\n\ + int index = x + y * 8;\n\ + float limit = 0.0;\n\ + if (x < 8) {\n\ + if(index==0) limit = 0.015625;\n\ + "+(LGraphShaderDither.dither_values.map( function(v,i){ return "else if(index== "+(i+1)+") limit = " + v + ";"}).join("\n"))+"\n\ + }\n\ + return brightness < limit ? 0.0 : 1.0;\n\ + }\n", + + registerShaderNode( "math/dither", LGraphShaderDither ); + + function LGraphShaderRemap() + { + this.addInput("","float,vec2,vec3,vec4"); + this.addOutput("",""); + this.properties = { + min_value: 0, + max_value: 1, + min_value2: 0, + max_value2: 1 + }; + this.addWidget("number","min",0,{ step: 0.1, property: "min_value" }); + this.addWidget("number","max",1,{ step: 0.1, property: "max_value" }); + this.addWidget("number","min2",0,{ step: 0.1, property: "min_value2"}); + this.addWidget("number","max2",1,{ step: 0.1, property: "max_value2"}); + } + + LGraphShaderRemap.title = "Remap"; + + LGraphShaderRemap.prototype.onPropertyChanged = function() + { + this.graph._version++; + } + + LGraphShaderRemap.prototype.onConnectionsChange = function() + { + var return_type = this.getInputDataType(0); + this.outputs[0].type = return_type || "T"; + } + + LGraphShaderRemap.prototype.onGetCode = function( context ) + { + if(!this.isOutputConnected(0)) + return; + + var inlink = getInputLinkID(this,0); + var outlink = getOutputLinkID(this,0); + if(!inlink && !outlink) //not connected + return; + + var return_type = this.getInputDataType(0); + this.outputs[0].type = return_type; + if(return_type == "T") + { + console.warn("node type is T and cannot be resolved"); + return; + } + + if(!inlink) + { + context.addCode("code"," " + return_type + " " + outlink + " = " + return_type + "(0.0);\n"); + return; + } + + var minv = valueToGLSL( this.properties.min_value ); + var maxv = valueToGLSL( this.properties.max_value ); + var minv2 = valueToGLSL( this.properties.min_value2 ); + var maxv2 = valueToGLSL( this.properties.max_value2 ); + + context.addCode("code", return_type + " " + outlink + " = ( (" + inlink + " - "+minv+") / ("+ maxv+" - "+minv+") ) * ("+ maxv2+" - "+minv2+") + " + minv2 + ";", this.shader_destination ); + this.setOutputData( 0, return_type ); + } + + registerShaderNode( "math/remap", LGraphShaderRemap ); + +})(this); + + diff --git a/src/nodes/gltextures.js b/src/nodes/gltextures.js index adfbf1856..064be4264 100755 --- a/src/nodes/gltextures.js +++ b/src/nodes/gltextures.js @@ -1,5 +1,6 @@ (function(global) { var LiteGraph = global.LiteGraph; + var LGraphCanvas = global.LGraphCanvas; //Works with Litegl.js to create WebGL nodes global.LGraphTexture = null; @@ -33,14 +34,16 @@ LGraphTexture.image_preview_size = 256; //flags to choose output texture type - LGraphTexture.PASS_THROUGH = 1; //do not apply FX + LGraphTexture.UNDEFINED = 0; //not specified + LGraphTexture.PASS_THROUGH = 1; //do not apply FX (like disable but passing the in to the out) 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.DEFAULT = 2; //use the default LGraphTexture.MODE_VALUES = { + "undefined": LGraphTexture.UNDEFINED, "pass through": LGraphTexture.PASS_THROUGH, copy: LGraphTexture.COPY, low: LGraphTexture.LOW, @@ -113,11 +116,12 @@ !target || target.width != origin.width || target.height != origin.height || - target.type != tex_type + target.type != tex_type || + target.format != origin.format ) { target = new GL.Texture(origin.width, origin.height, { type: tex_type, - format: gl.RGBA, + format: origin.format, filter: gl.LINEAR }); } @@ -659,7 +663,7 @@ u_texture: 0, u_textureB: 1, value: value, - texSize: [width, height], + texSize: [width, height,1/width,1/height], time: time }) .draw(mesh); @@ -674,7 +678,7 @@ uniform sampler2D u_texture;\n\ uniform sampler2D u_textureB;\n\ varying vec2 v_coord;\n\ - uniform vec2 texSize;\n\ + uniform vec4 texSize;\n\ uniform float time;\n\ uniform float value;\n\ \n\ @@ -711,6 +715,20 @@ LGraphTextureOperation.registerPreset("displace","texture2D(u_texture, uv + (colorB.xy - vec2(0.5)) * value).xyz"); LGraphTextureOperation.registerPreset("grayscale","vec3(color.x + color.y + color.z) * value / 3.0"); LGraphTextureOperation.registerPreset("saturation","mix( vec3(color.x + color.y + color.z) / 3.0, color, value )"); + LGraphTextureOperation.registerPreset("normalmap","\n\ + float z0 = texture2D(u_texture, uv + vec2(-texSize.z, -texSize.w) ).x;\n\ + float z1 = texture2D(u_texture, uv + vec2(0.0, -texSize.w) ).x;\n\ + float z2 = texture2D(u_texture, uv + vec2(texSize.z, -texSize.w) ).x;\n\ + float z3 = texture2D(u_texture, uv + vec2(-texSize.z, 0.0) ).x;\n\ + float z4 = color.x;\n\ + float z5 = texture2D(u_texture, uv + vec2(texSize.z, 0.0) ).x;\n\ + float z6 = texture2D(u_texture, uv + vec2(-texSize.z, texSize.w) ).x;\n\ + float z7 = texture2D(u_texture, uv + vec2(0.0, texSize.w) ).x;\n\ + float z8 = texture2D(u_texture, uv + vec2(texSize.z, texSize.w) ).x;\n\ + vec3 normal = vec3( z2 + 2.0*z4 + z7 - z0 - 2.0*z3 - z5, z5 + 2.0*z6 + z7 -z0 - 2.0*z1 - z2, 1.0 );\n\ + normal.xy *= value;\n\ + result.xyz = normalize(normal) * 0.5 + vec3(0.5);\n\ + "); LGraphTextureOperation.registerPreset("threshold","vec3(color.x > colorB.x * value ? 1.0 : 0.0,color.y > colorB.y * value ? 1.0 : 0.0,color.z > colorB.z * value ? 1.0 : 0.0)"); //webglstudio stuff... @@ -742,15 +760,14 @@ precision: LGraphTexture.DEFAULT }; - this.properties.code = - "//time: time in seconds\n//texSize: vec2 with res\nuniform float u_value;\nuniform vec4 u_color;\n\nvoid main() {\n vec2 uv = v_coord;\n vec3 color = vec3(0.0);\n //your code here\n color.xy=uv;\n\ngl_FragColor = vec4(color, 1.0);\n}\n"; - this._uniforms = { u_value: 1, u_color: vec4.create(), in_texture: 0, texSize: vec2.create(), time: 0 }; + this.properties.code = LGraphTextureShader.pixel_shader; + this._uniforms = { u_value: 1, u_color: vec4.create(), in_texture: 0, texSize: vec4.create(), time: 0 }; } LGraphTextureShader.title = "Shader"; LGraphTextureShader.desc = "Texture shader"; LGraphTextureShader.widgets_info = { - code: { type: "code" }, + code: { type: "code", lang: "glsl" }, precision: { widget: "combo", values: LGraphTexture.MODE_VALUES } }; @@ -853,10 +870,7 @@ } this._shader_code = this.properties.code; - this._shader = new GL.Shader( - Shader.SCREEN_VERTEX_SHADER, - LGraphTextureShader.pixel_shader + this.properties.code - ); + this._shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, this.properties.code ); if (!this._shader) { this.boxcolor = "red"; return null; @@ -913,6 +927,8 @@ } uniforms.texSize[0] = w; uniforms.texSize[1] = h; + uniforms.texSize[2] = 1/w; + uniforms.texSize[3] = 1/h; uniforms.time = this.graph.getTime(); uniforms.u_value = this.properties.u_value; uniforms.u_color.set( this.properties.u_color ); @@ -929,10 +945,20 @@ }; LGraphTextureShader.pixel_shader = - "precision highp float;\n\ - \n\ - varying vec2 v_coord;\n\ - uniform float time;\n\ +"precision highp float;\n\ +\n\ +varying vec2 v_coord;\n\ +uniform float time; //time in seconds\n\ +uniform vec4 texSize; //tex resolution\n\ +uniform float u_value;\n\ +uniform vec4 u_color;\n\n\ +void main() {\n\ + vec2 uv = v_coord;\n\ + vec3 color = vec3(0.0);\n\ + //your code here\n\ + color.xy=uv;\n\n\ + gl_FragColor = vec4(color, 1.0);\n\ +}\n\ "; LiteGraph.registerNodeType("texture/shader", LGraphTextureShader); @@ -1206,6 +1232,20 @@ LGraphTextureToViewport.desc = "Texture to viewport"; LGraphTextureToViewport._prev_viewport = new Float32Array(4); + + LGraphTextureToViewport.prototype.onDrawBackground = function( ctx ) + { + if ( this.flags.collapsed || this.size[1] <= 40 ) + return; + + var tex = this.getInputData(0); + if (!tex) { + return; + } + + ctx.drawImage( ctx == gl ? tex : gl.canvas, 10,30, this.size[0] -20, this.size[1] -40); + } + LGraphTextureToViewport.prototype.onExecute = function() { var tex = this.getInputData(0); if (!tex) { @@ -1573,6 +1613,69 @@ LGraphTextureDownsample ); + + + function LGraphTextureResize() { + this.addInput("Texture", "Texture"); + this.addOutput("", "Texture"); + this.properties = { + size: [512,512], + generate_mipmaps: false, + precision: LGraphTexture.DEFAULT + }; + } + + LGraphTextureResize.title = "Resize"; + LGraphTextureResize.desc = "Resize Texture"; + LGraphTextureResize.widgets_info = { + iterations: { type: "number", step: 1, precision: 0, min: 0 }, + precision: { widget: "combo", values: LGraphTexture.MODE_VALUES } + }; + + LGraphTextureResize.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 width = this.properties.size[0] | 0; + var height = this.properties.size[1] | 0; + if(width == 0) + width = tex.width; + if(height == 0) + height = tex.height; + 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( !this._texture || this._texture.width != width || this._texture.height != height || this._texture.type != type ) + this._texture = new GL.Texture( width, height, { type: type } ); + + tex.copyTo( this._texture ); + + if (this.properties.generate_mipmaps) { + this._texture.bind(0); + gl.generateMipmap(this._texture.texture_type); + this._texture.unbind(0); + } + + this.setOutputData(0, this._texture); + }; + + LiteGraph.registerNodeType( "texture/resize", LGraphTextureResize ); + // Texture Average ***************************************** function LGraphTextureAverage() { this.addInput("Texture", "Texture"); @@ -2234,6 +2337,130 @@ LiteGraph.registerNodeType("texture/LUT", LGraphTextureLUT); + + // Texture LUT ***************************************** + function LGraphTextureEncode() { + this.addInput("Texture", "Texture"); + this.addInput("Atlas", "Texture"); + this.addOutput("", "Texture"); + this.properties = { enabled: true, num_row_symbols: 4, symbol_size: 16, brightness: 1, colorize: false, filter: false, invert: false, precision: LGraphTexture.DEFAULT, generate_mipmaps: false, texture: null }; + + if (!LGraphTextureEncode._shader) { + LGraphTextureEncode._shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphTextureEncode.pixel_shader ); + } + + this._uniforms = { + u_texture: 0, + u_textureB: 1, + u_row_simbols: 4, + u_simbol_size: 16, + u_res: vec2.create() + }; + } + + LGraphTextureEncode.widgets_info = { + texture: { widget: "texture" }, + precision: { widget: "combo", values: LGraphTexture.MODE_VALUES } + }; + + LGraphTextureEncode.title = "Encode"; + LGraphTextureEncode.desc = "Apply a texture atlas to encode a texture"; + + LGraphTextureEncode.prototype.onExecute = function() { + if (!this.isOutputConnected(0)) { + return; + } //saves work + + var tex = this.getInputData(0); + + if (this.properties.precision === LGraphTexture.PASS_THROUGH || this.properties.enabled === false) { + this.setOutputData(0, tex); + return; + } + + if (!tex) { + return; + } + + var symbols_tex = this.getInputData(1); + + if (!symbols_tex) { + symbols_tex = LGraphTexture.getTexture(this.properties.texture); + } + + if (!symbols_tex) { + this.setOutputData(0, tex); + return; + } + + symbols_tex.bind(0); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, this.properties.filter ? gl.LINEAR : gl.NEAREST ); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, this.properties.filter ? gl.LINEAR : gl.NEAREST ); + 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 uniforms = this._uniforms; + uniforms.u_row_simbols = Math.floor(this.properties.num_row_symbols); + uniforms.u_symbol_size = this.properties.symbol_size; + uniforms.u_brightness = this.properties.brightness; + uniforms.u_invert = this.properties.invert ? 1 : 0; + uniforms.u_colorize = this.properties.colorize ? 1 : 0; + + this._tex = LGraphTexture.getTargetTexture( tex, this._tex, this.properties.precision ); + uniforms.u_res[0] = this._tex.width; + uniforms.u_res[1] = this._tex.height; + this._tex.bind(0); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST ); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST ); + + this._tex.drawTo(function() { + symbols_tex.bind(1); + tex.toViewport(LGraphTextureEncode._shader, uniforms); + }); + + if (this.properties.generate_mipmaps) { + this._tex.bind(0); + gl.generateMipmap(this._tex.texture_type); + this._tex.unbind(0); + } + + this.setOutputData(0, this._tex); + }; + + LGraphTextureEncode.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_row_simbols;\n\ + uniform float u_symbol_size;\n\ + uniform float u_brightness;\n\ + uniform float u_invert;\n\ + uniform float u_colorize;\n\ + uniform vec2 u_res;\n\ + \n\ + void main() {\n\ + vec2 total_symbols = u_res / u_symbol_size;\n\ + vec2 uv = floor(v_coord * total_symbols) / total_symbols; //pixelate \n\ + vec2 local_uv = mod(v_coord * u_res, u_symbol_size) / u_symbol_size;\n\ + lowp vec4 textureColor = texture2D(u_texture, uv );\n\ + float lum = clamp(u_brightness * (textureColor.x + textureColor.y + textureColor.z)/3.0,0.0,1.0);\n\ + if( u_invert == 1.0 ) lum = 1.0 - lum;\n\ + float index = floor( lum * (u_row_simbols * u_row_simbols - 1.0));\n\ + float col = mod( index, u_row_simbols );\n\ + float row = u_row_simbols - floor( index / u_row_simbols ) - 1.0;\n\ + vec2 simbol_uv = ( vec2( col, row ) + local_uv ) / u_row_simbols;\n\ + vec4 color = texture2D( u_textureB, simbol_uv );\n\ + if(u_colorize == 1.0)\n\ + color *= textureColor;\n\ + gl_FragColor = color;\n\ + }\n\ + "; + + LiteGraph.registerNodeType("texture/encode", LGraphTextureEncode); + // Texture Channels ***************************************** function LGraphTextureChannels() { this.addInput("Texture", "Texture"); @@ -3044,8 +3271,8 @@ }; this._uniforms = { u_texture: 0, - u_near: 0.1, - u_far: 10000 + u_camera_planes: null, //filled later + u_ires: vec2.create() }; } @@ -3077,9 +3304,6 @@ } var uniforms = this._uniforms; - - uniforms.u_near = tex.near_far_planes[0]; - uniforms.u_far = tex.near_far_planes[1]; uniforms.u_invert = this.properties.invert ? 1 : 0; gl.disable(gl.BLEND); @@ -3099,6 +3323,8 @@ planes = [0.1, 1000]; } //hardcoded uniforms.u_camera_planes = planes; + //uniforms.u_ires.set([1/tex.width, 1/tex.height]); + uniforms.u_ires.set([0,0]); this._temp_texture.drawTo(function() { tex.bind(0); @@ -3114,15 +3340,14 @@ precision highp float;\n\ varying vec2 v_coord;\n\ uniform sampler2D u_texture;\n\ - uniform float u_near;\n\ - uniform float u_far;\n\ + uniform vec2 u_camera_planes;\n\ uniform int u_invert;\n\ + uniform vec2 u_ires;\n\ \n\ void main() {\n\ - float zNear = u_near;\n\ - float zFar = u_far;\n\ - float depth = texture2D(u_texture, v_coord).x;\n\ - depth = depth * 2.0 - 1.0;\n\ + float zNear = u_camera_planes.x;\n\ + float zFar = u_camera_planes.y;\n\ + float depth = texture2D(u_texture, v_coord + u_ires*0.5).x * 2.0 - 1.0;\n\ float f = zNear * (depth + 1.0) / (zFar + zNear - depth * (zFar - zNear));\n\ if( u_invert == 1 )\n\ f = 1.0 - f;\n\ @@ -4256,6 +4481,54 @@ void main(void){\n\ LiteGraph.registerNodeType("texture/lensfx", LGraphLensFX); + function LGraphTextureFromData() { + this.addInput("in", ""); + this.properties = { precision: LGraphTexture.LOW, width: 0, height: 0, channels: 1 }; + this.addOutput("out", "Texture"); + } + + LGraphTextureFromData.title = "Data->Tex"; + LGraphTextureFromData.desc = "Generates or applies a curve to a texture"; + LGraphTextureFromData.widgets_info = { + precision: { widget: "combo", values: LGraphTexture.MODE_VALUES } + }; + + LGraphTextureFromData.prototype.onExecute = function() { + if (!this.isOutputConnected(0)) { + return; + } //saves work + + var data = this.getInputData(0); + if(!data) + return; + + var channels = this.properties.channels; + var w = this.properties.width; + var h = this.properties.height; + if(!w || !h) + { + w = Math.floor(data.length / channels); + h = 1; + } + var format = gl.RGBA; + if( channels == 3 ) + format = gl.RGB; + else if( channels == 1 ) + format = gl.LUMINANCE; + + var temp = this._temp_texture; + var type = LGraphTexture.getTextureType( this.properties.precision ); + if ( !temp || temp.width != w || temp.height != h || temp.type != type ) { + temp = this._temp_texture = new GL.Texture( w, h, { type: type, format: format, filter: gl.LINEAR } ); + } + + temp.uploadData( data ); + this.setOutputData(0, temp); + } + + LiteGraph.registerNodeType("texture/fromdata", LGraphTextureFromData); + + //applies a curve (or generates one) function LGraphTextureCurve() { this.addInput("in", "Texture"); this.addOutput("out", "Texture"); @@ -4279,22 +4552,33 @@ void main(void){\n\ } LGraphTextureCurve.title = "Curve"; + LGraphTextureCurve.desc = "Generates or applies a curve to a texture"; + LGraphTextureCurve.widgets_info = { + precision: { widget: "combo", values: LGraphTexture.MODE_VALUES } + }; LGraphTextureCurve.prototype.onExecute = function() { - var tex = this.getInputData(0); - if (!tex) { - return; - } - if (!this.isOutputConnected(0)) { return; } //saves work + var tex = this.getInputData(0); + 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 } ); + if(!tex) //generate one texture, nothing else + { + if(this._must_update || !this._curve_texture ) + this.updateCurve(); + this.setOutputData(0, this._curve_texture); + return; } + var type = LGraphTexture.getTextureType( this.properties.precision, tex ); + + //apply curve to input texture + if ( !temp || temp.type != type || temp.width != tex.width || temp.height != tex.height || temp.format != tex.format) + temp = this._temp_texture = new GL.Texture( tex.width, tex.height, { type: type, format: tex.format, filter: gl.LINEAR } ); + var shader = LGraphTextureCurve._shader; if (!shader) { shader = LGraphTextureCurve._shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureCurve.pixel_shader ); @@ -4315,7 +4599,7 @@ void main(void){\n\ }); this.setOutputData(0, temp); - }; + } LGraphTextureCurve.prototype.sampleCurve = function(f,points) { @@ -4732,18 +5016,18 @@ void main(void){\n\ LGraphTexturePerlin.widgets_info = { precision: { widget: "combo", values: LGraphTexture.MODE_VALUES }, - width: { type: "Number", precision: 0, step: 1 }, - height: { type: "Number", precision: 0, step: 1 }, - octaves: { type: "Number", precision: 0, step: 1, min: 1, max: 50 } + width: { type: "number", precision: 0, step: 1 }, + height: { type: "number", precision: 0, step: 1 }, + octaves: { type: "number", precision: 0, step: 1, min: 1, max: 50 } }; LGraphTexturePerlin.prototype.onGetInputs = function() { return [ - ["seed", "Number"], - ["persistence", "Number"], - ["octaves", "Number"], - ["scale", "Number"], - ["amplitude", "Number"], + ["seed", "number"], + ["persistence", "number"], + ["octaves", "number"], + ["scale", "number"], + ["amplitude", "number"], ["offset", "vec2"] ]; }; @@ -4893,7 +5177,7 @@ void main(void){\n\ this.addInput("v"); this.addOutput("out", "Texture"); this.properties = { - code: "", + code: LGraphTextureCanvas2D.default_code, width: 512, height: 512, clear: true, @@ -4902,17 +5186,20 @@ void main(void){\n\ }; this._func = null; this._temp_texture = null; + this.compileCode(); } LGraphTextureCanvas2D.title = "Canvas2D"; LGraphTextureCanvas2D.desc = "Executes Canvas2D code inside a texture or the viewport."; LGraphTextureCanvas2D.help = "Set width and height to 0 to match viewport size."; + LGraphTextureCanvas2D.default_code = "//vars: canvas,ctx,time\nctx.fillStyle='red';\nctx.fillRect(0,0,50,50);\n"; + LGraphTextureCanvas2D.widgets_info = { precision: { widget: "combo", values: LGraphTexture.MODE_VALUES }, code: { type: "code" }, - width: { type: "Number", precision: 0, step: 1 }, - height: { type: "Number", precision: 0, step: 1 } + width: { type: "number", precision: 0, step: 1 }, + height: { type: "number", precision: 0, step: 1 } }; LGraphTextureCanvas2D.prototype.onPropertyChanged = function( name, value ) { diff --git a/src/nodes/graphics.js b/src/nodes/graphics.js index 4690ad590..e5cc9cbc5 100755 --- a/src/nodes/graphics.js +++ b/src/nodes/graphics.js @@ -143,9 +143,7 @@ if (callback) { callback(this); } - that.trace( - "Image loaded, size: " + that.img.width + "x" + that.img.height - ); + console.log( "Image loaded, size: " + that.img.width + "x" + that.img.height ); this.dirty = true; that.boxcolor = "#9F9"; that.setDirtyCanvas(true); @@ -427,7 +425,7 @@ if (name == "scale") { this.properties[name] = parseFloat(value); if (this.properties[name] == 0) { - this.trace("Error in scale"); + console.error("Error in scale"); this.properties[name] = 1.0; } } else { @@ -597,10 +595,23 @@ ImageVideo.prototype.loadVideo = function(url) { this._video_url = url; + var pos = url.substr(0,10).indexOf(":"); + var protocol = ""; + if(pos != -1) + protocol = url.substr(0,pos); + + var host = ""; + if(protocol) + { + host = url.substr(0,url.indexOf("/",protocol.length + 3)); + host = host.substr(protocol.length+3); + } + if ( this.properties.use_proxy && - url.substr(0, 4) == "http" && - LiteGraph.proxy + protocol && + LiteGraph.proxy && + host != location.host ) { url = LiteGraph.proxy + url.substr(url.indexOf(":") + 3); } @@ -615,41 +626,38 @@ var that = this; this._video.addEventListener("loadedmetadata", function(e) { //onload - that.trace("Duration: " + this.duration + " seconds"); - that.trace("Size: " + this.videoWidth + "," + this.videoHeight); + console.log("Duration: " + this.duration + " seconds"); + console.log("Size: " + this.videoWidth + "," + this.videoHeight); that.setDirtyCanvas(true); this.width = this.videoWidth; this.height = this.videoHeight; }); this._video.addEventListener("progress", function(e) { //onload - //that.trace("loading..."); + console.log("video loading..."); }); this._video.addEventListener("error", function(e) { - console.log("Error loading video: " + this.src); - that.trace("Error loading video: " + this.src); + console.error("Error loading video: " + this.src); if (this.error) { switch (this.error.code) { case this.error.MEDIA_ERR_ABORTED: - that.trace("You stopped the video."); + console.error("You stopped the video."); break; case this.error.MEDIA_ERR_NETWORK: - that.trace("Network error - please try again later."); + console.error("Network error - please try again later."); break; case this.error.MEDIA_ERR_DECODE: - that.trace("Video is broken.."); + console.error("Video is broken.."); break; case this.error.MEDIA_ERR_SRC_NOT_SUPPORTED: - that.trace( - "Sorry, your browser can't play this video." - ); + console.error("Sorry, your browser can't play this video."); break; } } }); this._video.addEventListener("ended", function(e) { - that.trace("Ended."); + console.log("Video Ended."); this.play(); //loop }); @@ -666,7 +674,7 @@ }; ImageVideo.prototype.play = function() { - if (this._video) { + if (this._video && this._video.videoWidth ) { //is loaded this._video.play(); } }; @@ -694,7 +702,7 @@ if (!this._video) { return; } - this.trace("Video paused"); + console.log("Video paused"); this._video.pause(); }; diff --git a/src/nodes/math.js b/src/nodes/math.js index b8a7821ec..601d8beba 100755 --- a/src/nodes/math.js +++ b/src/nodes/math.js @@ -110,6 +110,7 @@ function MathRange() { this.addInput("in", "number", { locked: true }); this.addOutput("out", "number", { locked: true }); + this.addOutput("clamped", "number", { locked: true }); this.addProperty("in", 0); this.addProperty("in_min", 0); @@ -117,7 +118,7 @@ this.addProperty("out_min", 0); this.addProperty("out_max", 1); - this.size = [80, 30]; + this.size = [120, 50]; } MathRange.title = "Range"; @@ -151,10 +152,22 @@ var in_max = this.properties.in_max; var out_min = this.properties.out_min; var out_max = this.properties.out_max; + /* + if( in_min > in_max ) + { + in_min = in_max; + in_max = this.properties.in_min; + } + if( out_min > out_max ) + { + out_min = out_max; + out_max = this.properties.out_min; + } + */ - this._last_v = - ((v - in_min) / (in_max - in_min)) * (out_max - out_min) + out_min; + this._last_v = ((v - in_min) / (in_max - in_min)) * (out_max - out_min) + out_min; this.setOutputData(0, this._last_v); + this.setOutputData(1, Math.clamp( this._last_v, out_min, out_max )); }; MathRange.prototype.onDrawBackground = function(ctx) { @@ -223,6 +236,10 @@ this.addProperty("min", 0); this.addProperty("max", 1); this.addProperty("smooth", true); + this.addProperty("seed", 0); + this.addProperty("octaves", 1); + this.addProperty("persistence", 0.8); + this.addProperty("speed", 1); this.size = [90, 30]; } @@ -253,7 +270,22 @@ MathNoise.prototype.onExecute = function() { var f = this.getInputData(0) || 0; - var r = MathNoise.getValue(f, this.properties.smooth); + var iterations = this.properties.octaves || 1; + var r = 0; + var amp = 1; + var seed = this.properties.seed || 0; + f += seed; + var speed = this.properties.speed || 1; + var total_amp = 0; + for(var i = 0; i < iterations; ++i) + { + r += MathNoise.getValue(f * (1+i) * speed, this.properties.smooth) * amp; + total_amp += amp; + amp *= this.properties.persistence; + if(amp < 0.001) + break; + } + r /= total_amp; var min = this.properties.min; var max = this.properties.max; this._last_v = r * (max - min) + min; @@ -595,12 +627,14 @@ //Math operation function MathOperation() { - this.addInput("A", "number"); + this.addInput("A", "number,array,object"); this.addInput("B", "number"); this.addOutput("=", "number"); this.addProperty("A", 1); this.addProperty("B", 1); this.addProperty("OP", "+", "enum", { values: MathOperation.values }); + this._func = function(A,B) { return A + B; }; + this._result = []; //only used for arrays } MathOperation.values = ["+", "-", "*", "/", "%", "^", "max", "min"]; @@ -627,11 +661,34 @@ this.properties["value"] = v; }; + MathOperation.prototype.onPropertyChanged = function(name, value) + { + if (name != "OP") + return; + switch (this.properties.OP) { + case "+": this._func = function(A,B) { return A + B; }; break; + case "-": this._func = function(A,B) { return A - B; }; break; + case "x": + case "X": + case "*": this._func = function(A,B) { return A * B; }; break; + case "/": this._func = function(A,B) { return A / B; }; break; + case "%": this._func = function(A,B) { return A % B; }; break; + case "^": this._func = function(A,B) { return Math.pow(A, B); }; break; + case "max": this._func = function(A,B) { return Math.max(A, B); }; break; + case "min": this._func = function(A,B) { return Math.min(A, B); }; break; + default: + console.warn("Unknown operation: " + this.properties.OP); + this._func = function(A) { return A; }; + break; + } + } + MathOperation.prototype.onExecute = function() { var A = this.getInputData(0); var B = this.getInputData(1); - if (A != null) { - this.properties["A"] = A; + if ( A != null ) { + if( A.constructor === Number ) + this.properties["A"] = A; } else { A = this.properties["A"]; } @@ -642,38 +699,26 @@ 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; - case "max": - result = Math.max(A, B); - break; - case "min": - result = Math.min(A, B); - break; - default: - console.warn("Unknown operation: " + this.properties.OP); - } - this.setOutputData(0, result); + var result; + if(A.constructor === Number) + { + result = 0; + result = this._func(A,B); + } + else if(A.constructor === Array) + { + result = this._result; + result.length = A.length; + for(var i = 0; i < A.length; ++i) + result[i] = this._func(A[i],B); + } + else + { + result = {}; + for(var i in A) + result[i] = this._func(A[i],B); + } + this.setOutputData(0, result); }; MathOperation.prototype.onDrawBackground = function(ctx) { @@ -704,6 +749,7 @@ title: "MIN()" }); + //Math compare function MathCompare() { this.addInput("A", "number"); @@ -808,6 +854,7 @@ this.addProperty("A", 1); this.addProperty("B", 1); this.addProperty("OP", ">", "enum", { values: MathCondition.values }); + this.addWidget("combo","Cond.",this.properties.OP,{ property: "OP", values: MathCondition.values } ); this.size = [80, 60]; } @@ -875,6 +922,37 @@ LiteGraph.registerNodeType("math/condition", MathCondition); + + function MathBranch() { + this.addInput("in", ""); + this.addInput("cond", "boolean"); + this.addOutput("true", ""); + this.addOutput("false", ""); + this.size = [80, 60]; + } + + MathBranch.title = "Branch"; + MathBranch.desc = "If condition is true, outputs IN in true, otherwise in false"; + + MathBranch.prototype.onExecute = function() { + var V = this.getInputData(0); + var cond = this.getInputData(1); + + if(cond) + { + this.setOutputData(0, V); + this.setOutputData(1, null); + } + else + { + this.setOutputData(0, null); + this.setOutputData(1, V); + } + } + + LiteGraph.registerNodeType("math/branch", MathBranch); + + function MathAccumulate() { this.addInput("inc", "number"); this.addOutput("total", "number"); @@ -1247,141 +1325,4 @@ 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); diff --git a/src/nodes/math3d.js b/src/nodes/math3d.js index 176588b68..453f19060 100644 --- a/src/nodes/math3d.js +++ b/src/nodes/math3d.js @@ -68,12 +68,22 @@ function Math3DOperation() { this.addInput("A", "number,vec3"); this.addInput("B", "number,vec3"); - this.addOutput("=", "vec3"); + this.addOutput("=", "number,vec3"); this.addProperty("OP", "+", "enum", { values: Math3DOperation.values }); this._result = vec3.create(); } - Math3DOperation.values = ["+", "-", "*", "/", "%", "^", "max", "min"]; + Math3DOperation.values = ["+", "-", "*", "/", "%", "^", "max", "min","dot","cross"]; + + LiteGraph.registerSearchboxExtra("math3d/operation", "CROSS()", { + properties: {"OP":"cross"}, + title: "CROSS()" + }); + + LiteGraph.registerSearchboxExtra("math3d/operation", "DOT()", { + properties: {"OP":"dot"}, + title: "DOT()" + }); Math3DOperation.title = "Operation"; Math3DOperation.desc = "Easy math 3D operators"; @@ -135,6 +145,11 @@ result[0] = Math.min(A[0],B[0]); result[1] = Math.min(A[1],B[1]); result[2] = Math.min(A[2],B[2]); + case "dot": + result = vec3.dot(A,B); + break; + case "cross": + vec3.cross(result,A,B); break; default: console.warn("Unknown operation: " + this.properties.OP); @@ -352,6 +367,53 @@ LiteGraph.registerNodeType("math3d/rotation", Math3DRotation); + + function MathEulerToQuat() { + this.addInput("euler", "vec3"); + this.addOutput("quat", "quat"); + this.properties = { euler:[0,0,0], use_yaw_pitch_roll: false }; + this._degs = vec3.create(); + this._value = quat.create(); + } + + MathEulerToQuat.title = "Euler->Quat"; + MathEulerToQuat.desc = "Converts euler angles (in degrees) to quaternion"; + + MathEulerToQuat.prototype.onExecute = function() { + var euler = this.getInputData(0); + if (euler == null) { + euler = this.properties.euler; + } + vec3.scale( this._degs, euler, DEG2RAD ); + if(this.properties.use_yaw_pitch_roll) + this._degs = [this._degs[2],this._degs[0],this._degs[1]]; + var R = quat.fromEuler(this._value, this._degs); + this.setOutputData(0, R); + }; + + LiteGraph.registerNodeType("math3d/euler_to_quat", MathEulerToQuat); + + function MathQuatToEuler() { + this.addInput(["quat", "quat"]); + this.addOutput("euler", "vec3"); + this._value = vec3.create(); + } + + MathQuatToEuler.title = "Euler->Quat"; + MathQuatToEuler.desc = "Converts rotX,rotY,rotZ in degrees to quat"; + + MathQuatToEuler.prototype.onExecute = function() { + var q = this.getInputData(0); + if(!q) + return; + var R = quat.toEuler(this._value, q); + vec3.scale( this._value, this._value, DEG2RAD ); + this.setOutputData(0, this._value); + }; + + LiteGraph.registerNodeType("math3d/quat_to_euler", MathQuatToEuler); + + //Math3D rotate vec3 function Math3DRotateVec3() { this.addInputs([["vec3", "vec3"], ["quat", "quat"]]); @@ -464,6 +526,21 @@ var target_min = this.properties.target_min; var target_max = this.properties.target_max; + //swap to avoid errors + /* + if(range_min > range_max) + { + range_min = range_max; + range_max = this.properties.range_min; + } + + if(target_min > target_max) + { + target_min = target_max; + target_max = this.properties.target_min; + } + */ + for(var i = 0; i < 3; ++i) { var r = range_max[i] - range_min[i]; diff --git a/src/nodes/midi.js b/src/nodes/midi.js index 1a20122ed..e386f91ab 100644 --- a/src/nodes/midi.js +++ b/src/nodes/midi.js @@ -328,7 +328,7 @@ MIDIEvent.commands_reversed[MIDIEvent.commands[i]] = i; } - //MIDI wrapper + //MIDI wrapper, instantiate by MIDIIn and MIDIOut function MIDIInterface(on_ready, on_error) { if (!navigator.requestMIDIAccess) { this.error = "not suppoorted"; @@ -347,9 +347,12 @@ cc: [] }; - navigator - .requestMIDIAccess() - .then(this.onMIDISuccess.bind(this), this.onMIDIFailure.bind(this)); + this.input_ports = null; + this.input_ports_info = []; + this.output_ports = null; + this.output_ports_info = []; + + navigator.requestMIDIAccess().then(this.onMIDISuccess.bind(this), this.onMIDIFailure.bind(this)); } MIDIInterface.input = null; @@ -370,80 +373,34 @@ MIDIInterface.prototype.updatePorts = function() { var midi = this.midi; this.input_ports = midi.inputs; + this.input_ports_info = []; + this.output_ports = midi.outputs; + this.output_ports_info = []; + 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 + - "'" - ); + this.input_ports_info.push(port_info); + 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 + - "'" - ); + this.output_ports_info.push(port_info); + 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) { @@ -493,7 +450,7 @@ return; } - var output_port = this.output_ports.get("output-" + port); + var output_port = this.output_ports_info[port];//this.output_ports.get("output-" + port); if (!output_port) { return; } @@ -540,10 +497,9 @@ 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; + for (var i = 0; i < this._midi.input_ports_info.length; ++i) { + var input = this._midi.input_ports_info[i]; + values[i] = i + ".- " + input.name + " version:" + input.version; } return { type: "enum", values: values }; } @@ -641,7 +597,10 @@ var that = this; new MIDIInterface(function(midi) { that._midi = midi; + that.widget.options.values = that.getMIDIOutputs(); }); + this.widget = this.addWidget("combo","Device",this.properties.port,{ property: "port", values: this.getMIDIOutputs.bind(this) }); + this.size = [340,60]; } LGMIDIOut.MIDIInterface = MIDIInterface; @@ -650,21 +609,33 @@ LGMIDIOut.desc = "Sends MIDI to output channel"; LGMIDIOut.color = MIDI_COLOR; - LGMIDIOut.prototype.getPropertyInfo = function(name) { + LGMIDIOut.prototype.onGetPropertyInfo = 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; - } + var values = this.getMIDIOutputs(); return { type: "enum", values: values }; } }; + LGMIDIOut.default_ports = {0:"unknown"}; + + LGMIDIOut.prototype.getMIDIOutputs = function() + { + var values = {}; + if(!this._midi) + return LGMIDIOut.default_ports; + if(this._midi.output_ports_info) + for (var i = 0; i < this._midi.output_ports_info.length; ++i) { + var output = this._midi.output_ports_info[i]; + if(!output) + continue; + var name = i + ".- " + output.name + " version:" + output.version; + values[i] = name; + } + return values; + } LGMIDIOut.prototype.onAction = function(event, midi_event) { //console.log(midi_event); @@ -672,7 +643,7 @@ return; } if (event == "send") { - this._midi.sendMIDI(this.port, midi_event); + this._midi.sendMIDI(this.properties.port, midi_event); } this.trigger("midi", midi_event); }; @@ -687,6 +658,7 @@ LiteGraph.registerNodeType("midi/output", LGMIDIOut); + function LGMIDIShow() { this.addInput("on_midi", LiteGraph.EVENT); this._str = ""; @@ -910,6 +882,24 @@ this.properties.value1 = (v | 0) % 255; } break; + case "cmd": + var v = this.getInputData(i); + if (v != null) { + this.properties.cmd = v; + } + break; + case "value1": + var v = this.getInputData(i); + if (v != null) { + this.properties.value1 = Math.clamp(v|0,0,127); + } + break; + case "value2": + var v = this.getInputData(i); + if (v != null) { + this.properties.value2 = Math.clamp(v|0,0,127); + } + break; } } } @@ -978,7 +968,7 @@ }; LGMIDIEvent.prototype.onGetInputs = function() { - return [["note", "number"]]; + return [["cmd", "number"],["note", "number"],["value1", "number"],["value2", "number"]]; }; LGMIDIEvent.prototype.onGetOutputs = function() { @@ -1235,6 +1225,122 @@ LiteGraph.registerNodeType("midi/quantize", LGMIDIQuantize); + function LGMIDIFromFile() { + this.properties = { + url: "", + autoplay: true + }; + + this.addInput("play", LiteGraph.ACTION); + this.addInput("pause", LiteGraph.ACTION); + this.addOutput("note", LiteGraph.EVENT); + this._midi = null; + this._current_time = 0; + this._playing = false; + + if (typeof MidiParser == "undefined") { + console.error( + "midi-parser.js not included, LGMidiPlay requires that library: https://raw.githubusercontent.com/colxi/midi-parser-js/master/src/main.js" + ); + this.boxcolor = "red"; + } + + } + + LGMIDIFromFile.title = "MIDI fromFile"; + LGMIDIFromFile.desc = "Plays a MIDI file"; + LGMIDIFromFile.color = MIDI_COLOR; + + LGMIDIFromFile.prototype.onAction = function( name ) + { + if(name == "play") + this.play(); + else if(name == "pause") + this._playing = !this._playing; + } + + LGMIDIFromFile.prototype.onPropertyChanged = function(name,value) + { + if(name == "url") + this.loadMIDIFile(value); + } + + LGMIDIFromFile.prototype.onExecute = function() { + if(!this._midi) + return; + + if(!this._playing) + return; + + this._current_time += this.graph.elapsed_time; + var current_time = this._current_time * 100; + + for(var i = 0; i < this._midi.tracks; ++i) + { + var track = this._midi.track[i]; + if(!track._last_pos) + { + track._last_pos = 0; + track._time = 0; + } + + var elem = track.event[ track._last_pos ]; + if(elem && (track._time + elem.deltaTime) <= current_time ) + { + track._last_pos++; + track._time += elem.deltaTime; + + if(elem.data) + { + var midi_cmd = elem.type << 4 + elem.channel; + var midi_event = new MIDIEvent(); + midi_event.setup([midi_cmd, elem.data[0], elem.data[1]]); + this.trigger("note", midi_event); + } + } + + } + }; + + LGMIDIFromFile.prototype.play = function() + { + this._playing = true; + this._current_time = 0; + if(!this._midi) + return; + + for(var i = 0; i < this._midi.tracks; ++i) + { + var track = this._midi.track[i]; + track._last_pos = 0; + track._time = 0; + } + } + + LGMIDIFromFile.prototype.loadMIDIFile = function(url) + { + var that = this; + LiteGraph.fetchFile( url, "arraybuffer", function(data) + { + that.boxcolor = "#AFA"; + that._midi = MidiParser.parse( new Uint8Array(data) ); + if(that.properties.autoplay) + that.play(); + }, function(err){ + that.boxcolor = "#FAA"; + that._midi = null; + }); + } + + LGMIDIFromFile.prototype.onDropFile = function(file) + { + this.properties.url = ""; + this.loadMIDIFile( file ); + } + + LiteGraph.registerNodeType("midi/fromFile", LGMIDIFromFile); + + function LGMIDIPlay() { this.properties = { volume: 0.5, diff --git a/src/nodes/network.js b/src/nodes/network.js index 07bd72c12..0deef5c79 100644 --- a/src/nodes/network.js +++ b/src/nodes/network.js @@ -86,10 +86,10 @@ this._ws.onmessage = function(e) { that.boxcolor = "#AFA"; var data = JSON.parse(e.data); - if (data.room && data.room != this.properties.room) { + if (data.room && data.room != that.properties.room) { return; } - if (e.data.type == 1) { + if (data.type == 1) { if ( data.data.object_class && LiteGraph[data.data.object_class] @@ -105,7 +105,7 @@ that.triggerSlot(0, data.data); } } else { - that._last_received_data[e.data.channel || 0] = data.data; + that._last_received_data[data.channel || 0] = data.data; } }; this._ws.onerror = function(e) { diff --git a/src/nodes/strings.js b/src/nodes/strings.js index b84b3af0e..d6dd9d13f 100644 --- a/src/nodes/strings.js +++ b/src/nodes/strings.js @@ -6,7 +6,7 @@ return String(a); } - LiteGraph.wrapFunctionAsNode("string/toString", compare, ["*"], "String"); + LiteGraph.wrapFunctionAsNode("string/toString", compare, [""], "String"); function compare(a, b) { return a == b; @@ -15,8 +15,8 @@ LiteGraph.wrapFunctionAsNode( "string/compare", compare, - ["String", "String"], - "Boolean" + ["string", "string"], + "boolean" ); function concatenate(a, b) { @@ -32,8 +32,8 @@ LiteGraph.wrapFunctionAsNode( "string/concatenate", concatenate, - ["String", "String"], - "String" + ["string", "string"], + "string" ); function contains(a, b) { @@ -46,8 +46,8 @@ LiteGraph.wrapFunctionAsNode( "string/contains", contains, - ["String", "String"], - "Boolean" + ["string", "string"], + "boolean" ); function toUpperCase(a) { @@ -60,22 +60,33 @@ LiteGraph.wrapFunctionAsNode( "string/toUpperCase", toUpperCase, - ["String"], - "String" + ["string"], + "string" ); - function split(a, b) { - if (a != null && a.constructor === String) { - return a.split(b || " "); - } - return [a]; + function split(str, separator) { + if(separator == null) + separator = this.properties.separator; + if (str == null ) + return []; + if( str.constructor === String ) + return str.split(separator || " "); + else if( str.constructor === Array ) + { + var r = []; + for(var i = 0; i < str.length; ++i) + r[i] = str[i].split(separator || " "); + return r; + } + return null; } LiteGraph.wrapFunctionAsNode( "string/split", - toUpperCase, - ["String", "String"], - "Array" + split, + ["string,array", "string"], + "array", + { separator: "," } ); function toFixed(a) { @@ -88,8 +99,39 @@ LiteGraph.wrapFunctionAsNode( "string/toFixed", toFixed, - ["Number"], - "String", + ["number"], + "string", { precision: 0 } ); + + + function StringToTable() { + this.addInput("", "string"); + this.addOutput("table", "table"); + this.addOutput("rows", "number"); + this.addProperty("value", ""); + this.addProperty("separator", ","); + this._table = null; + } + + StringToTable.title = "toTable"; + StringToTable.desc = "Splits a string to table"; + + StringToTable.prototype.onExecute = function() { + var input = this.getInputData(0); + if(!input) + return; + var separator = this.properties.separator || ","; + if(input != this._str || separator != this._last_separator ) + { + this._last_separator = separator; + this._str = input; + this._table = input.split("\n").map(function(a){ return a.trim().split(separator)}); + } + this.setOutputData(0, this._table ); + this.setOutputData(1, this._table ? this._table.length : 0 ); + }; + + LiteGraph.registerNodeType("string/toTable", StringToTable); + })(this); diff --git a/utils/deploy_files.txt b/utils/deploy_files.txt index 03665ddf7..45163519b 100755 --- a/utils/deploy_files.txt +++ b/utils/deploy_files.txt @@ -9,6 +9,7 @@ ../src/nodes/logic.js ../src/nodes/graphics.js ../src/nodes/gltextures.js +../src/nodes/glshaders.js ../src/nodes/geometry.js ../src/nodes/glfx.js ../src/nodes/midi.js diff --git a/utils/server.js b/utils/server.js index e3242dc95..f413eda94 100644 --- a/utils/server.js +++ b/utils/server.js @@ -7,4 +7,4 @@ app.use('/external', express.static('external')) app.use('/demo', express.static('demo')) app.use('/', express.static('demo')) -app.listen(80, () => console.log('Example app listening on port 80!')) +app.listen(8000, () => console.log('Example app listening on port 8000!'))