From 7f4e5b8d79ed9b60b2286f47a1d689292d284569 Mon Sep 17 00:00:00 2001 From: tamat Date: Thu, 19 Mar 2020 08:57:36 +0100 Subject: [PATCH] fix --- build/litegraph.js | 471 +- build/litegraph.min.js | 1273 +-- css/litegraph-editor.css | 138 +- css/litegraph.css | 1 + demo/index.html | 1 + demo/js/code.js | 84 +- demo/js/demos.js | 1 - demo/js/libs/gl-matrix-min.js | 28 + demo/js/libs/litegl.js | 13432 ++++++++++++++++++++++++++++++++ src/litegraph-editor.js | 333 +- src/litegraph.js | 259 +- src/nodes/.gltextures.js.swp | Bin 40960 -> 0 bytes src/nodes/audio.js | 2 +- src/nodes/base.js | 58 +- src/nodes/events.js | 20 +- src/nodes/geometry.js | 76 +- src/nodes/gltextures.js | 33 +- src/nodes/midi.js | 23 +- 18 files changed, 15244 insertions(+), 989 deletions(-) create mode 100644 demo/js/libs/gl-matrix-min.js create mode 100644 demo/js/libs/litegl.js delete mode 100644 src/nodes/.gltextures.js.swp diff --git a/build/litegraph.js b/build/litegraph.js index 5c6655b76..1e2be9d25 100644 --- a/build/litegraph.js +++ b/build/litegraph.js @@ -134,8 +134,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) { @@ -163,11 +167,25 @@ }, enumerable: 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) { @@ -179,26 +197,22 @@ 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 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; - } - } }, + /** + * 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. @@ -683,24 +697,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); + if(that.onBeforeStep) + that.onBeforeStep(); that.runStep(1, !this.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 + if(that.onBeforeStep) + that.onBeforeStep(); that.runStep(1, !this.catch_errors); + if(that.onAfterStep) + that.onAfterStep(); }, interval); } }; @@ -1895,7 +1914,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]; } @@ -2205,6 +2224,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 ] ) ); } @@ -2267,7 +2288,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; } } @@ -2368,6 +2392,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 ************************* @@ -3132,13 +3168,51 @@ }; /** - * 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]; + + //litescene mode using the constructor + if (this.onGetPropertyInfo) { + info = this.onGetPropertyInfo(property); + } + + if (!info) + info = {}; + if(!info.type) + info.type = typeof this.properties[property]; + + 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 */ @@ -5454,16 +5528,8 @@ LGraphNode.prototype.executeAction = function(action) 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); @@ -5472,6 +5538,12 @@ LGraphNode.prototype.executeAction = function(action) this.last_mouse_dragging = false; if (e.which == 1) { + + if( this.node_widget ) + { + this.processNodeWidgets( this.node_widget[0], this.canvas_mouse, e ); + } + //left button this.node_widget = null; @@ -8282,7 +8354,7 @@ 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; if ( w == active_widget || (x > 6 && x < width - 12 && y > w.last_y && y < w.last_y + LiteGraph.NODE_WIDGET_HEIGHT) ) { //inside widget @@ -8316,18 +8388,11 @@ LGraphNode.prototype.executeAction = function(action) 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 += 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 - ) { + if ( w.options.max != null && w.value > w.options.max ) { w.value = w.options.max; } } else if (event.type == "mousedown") { @@ -8339,38 +8404,33 @@ LGraphNode.prototype.executeAction = function(action) 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 - ) { + 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 - ) { + 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; + } else if (delta) { //used for combos + var values_list = values.constructor === Array ? values : Object.keys(values); + var index = values_list.indexOf(w.value) + delta; if (index >= values.length) { index = 0; } if (index < 0) { - index = values.length - 1; + index = values_list.length - 1; } - w.value = values[index]; - } else { - var menu = new LiteGraph.ContextMenu( - values, - { + if( values.constructor === Array ) + w.value = values[index]; + else + w.value = values[ values_list[index] ]; + } else { //combo + var menu = new LiteGraph.ContextMenu(values,{ scale: Math.max(1, this.ds.scale), event: event, className: "dark", callback: inner_clicked.bind(w) }, - ref_window - ); + ref_window); function inner_clicked(v, option, event) { this.value = v; inner_value_change(this, v); @@ -8378,7 +8438,18 @@ LGraphNode.prototype.executeAction = function(action) return false; } } - } //mousedown + } //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), + event); + } + } if( old_value != w.value ) setTimeout( @@ -8400,15 +8471,11 @@ LGraphNode.prototype.executeAction = function(action) case "string": case "text": if (event.type == "mousedown") { - this.prompt( - "Value", - w.value, - function(v) { + this.prompt("Value",w.value,function(v) { this.value = v; inner_value_change(this, v); }.bind(w), - event - ); + event); } break; default: @@ -9423,11 +9490,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; } @@ -9435,28 +9498,8 @@ 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 = ""; @@ -9550,9 +9593,13 @@ LGraphNode.prototype.executeAction = function(action) if (node.onPropertyChanged) { node.onPropertyChanged(property, value); } + if(options.onclose) + options.onclose(); dialog.close(); node.setDirtyCanvas(true, true); } + + return dialog; }; LGraphCanvas.prototype.createDialog = function(html, options) { @@ -11182,12 +11229,17 @@ if (typeof exports != "undefined") { { var type = this.properties.type; this.type_widget.value = type; + if(this.outputs[0].type != type) + { + this.outputs[0].type = type; + this.disconnectOutput(0); + } 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; @@ -11253,8 +11305,10 @@ if (typeof exports != "undefined") { 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() { @@ -11374,6 +11428,14 @@ if (typeof exports != "undefined") { 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"; @@ -11390,10 +11452,6 @@ if (typeof exports != "undefined") { 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); @@ -11401,6 +11459,29 @@ if (typeof exports != "undefined") { 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"]); + }; + + LiteGraph.registerNodeType("basic/boolean", ConstantBoolean); + function ConstantString() { this.addOutput("", "string"); this.addProperty("value", ""); @@ -11408,23 +11489,15 @@ if (typeof exports != "undefined") { "text", "value", "", - this.setValue.bind(this) + "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() { @@ -11896,22 +11969,30 @@ if (typeof exports != "undefined") { //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); @@ -17053,15 +17134,14 @@ if (typeof exports != "undefined") { 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.properties.code = LGraphTextureShader.pixel_shader; 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" }, + code: { type: "code", lang: "glsl" }, precision: { widget: "combo", values: LGraphTexture.MODE_VALUES } }; @@ -17164,10 +17244,7 @@ if (typeof exports != "undefined") { } 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; @@ -17240,10 +17317,20 @@ if (typeof exports != "undefined") { }; 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 vec2 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); @@ -21204,7 +21291,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, @@ -21213,12 +21300,15 @@ 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" }, @@ -21530,8 +21620,9 @@ void main(void){\n\ 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 } @@ -21629,7 +21720,7 @@ void main(void){\n\ if(normals) { for(var i = 0; i < normals.length; i+=3) - normals.set(i, UP); + normals.set(UP, i); } } else if( mode == LGraphPoints3D.SPHERE) @@ -21660,7 +21751,7 @@ void main(void){\n\ if(normals) { for(var i = 0; i < normals.length; i+=3) - normals.set(i, UP); + normals.set(UP, i); } } } @@ -21677,7 +21768,7 @@ void main(void){\n\ if(normals) { for(var i = 0; i < normals.length; i+=3) - normals.set(i, UP); + normals.set(UP, i); } } else if( mode == LGraphPoints3D.CUBE) @@ -21691,7 +21782,7 @@ void main(void){\n\ if(normals) { for(var i = 0; i < normals.length; i+=3) - normals.set(i, UP); + normals.set(UP, i); } } else if( mode == LGraphPoints3D.SPHERE) @@ -21726,6 +21817,12 @@ void main(void){\n\ { 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"); } @@ -21931,6 +22028,36 @@ void main(void){\n\ } } + 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 ); @@ -21939,11 +22066,13 @@ void main(void){\n\ 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; @@ -21971,8 +22100,13 @@ void main(void){\n\ 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 ); } @@ -22076,7 +22210,8 @@ void main(void){\n\ this.geometry = { type: "triangles", vertices: null, - _id: generateGeometryId() + _id: generateGeometryId(), + _version: 0 }; this._last_geometry_id = -1; @@ -22195,7 +22330,7 @@ void main(void){\n\ 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", @@ -22233,6 +22368,13 @@ void main(void){\n\ 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; @@ -22242,6 +22384,12 @@ void main(void){\n\ 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; @@ -24747,6 +24895,8 @@ function LGraphGeometryDisplace() { new MIDIInterface(function(midi) { that._midi = midi; }); + + this.addWidget("combo","Device",this.properties.port,{ property: "port", values: this.getMIDIOutputs.bind(this) }); } LGMIDIOut.MIDIInterface = MIDIInterface; @@ -24755,22 +24905,28 @@ function LGraphGeometryDisplace() { 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.prototype.getMIDIOutputs = function() + { + var values = {}; + for (var i = 0; i < this._midi.output_ports.size; ++i) { + var output = this._midi.output_ports.get(i); + if(output) + values[i] = i + ".- " + output.name + " version:" + output.version; + } + return values; + } + LGMIDIOut.prototype.onAction = function(event, midi_event) { //console.log(midi_event); if (!this._midi) { @@ -24792,6 +24948,7 @@ function LGraphGeometryDisplace() { LiteGraph.registerNodeType("midi/output", LGMIDIOut); + function LGMIDIShow() { this.addInput("on_midi", LiteGraph.EVENT); this._str = ""; @@ -26943,7 +27100,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/build/litegraph.min.js b/build/litegraph.min.js index 8a7fe4940..515983274 100755 --- a/build/litegraph.min.js +++ b/build/litegraph.min.js @@ -1,702 +1,705 @@ -(function(y){function c(a){g.debug&&console.log("Graph created");this.list_of_graphcanvas=null;this.clear();a&&this.configure(a)}function m(a,b,d,k,f,g){this.id=a;this.type=b;this.origin_id=d;this.origin_slot=k;this.target_id=f;this.target_slot=g;this._data=null;this._pos=new Float32Array(2)}function n(a){this._ctor(a)}function l(a){this._ctor(a)}function x(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 e(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 x;this.zoom_modify_alpha=!0;this.title_text_font=""+g.NODE_TEXT_SIZE+"px Arial";this.inner_text_font="normal "+g.NODE_SUBTEXT_SIZE+"px Arial";this.node_title_color=g.NODE_TITLE_COLOR;this.default_link_color=g.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= +(function(y){function c(a){e.debug&&console.log("Graph created");this.list_of_graphcanvas=null;this.clear();a&&this.configure(a)}function m(a,b,d,h,f,e){this.id=a;this.type=b;this.origin_id=d;this.origin_slot=h;this.target_id=f;this.target_slot=e;this._data=null;this._pos=new Float32Array(2)}function n(a){this._ctor(a)}function k(a){this._ctor(a)}function x(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 x;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=g.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 C(a,b){return Math.sqrt((b[0]-a[0])*(b[0]-a[0])+(b[1]-a[1])*(b[1]-a[1]))}function z(a,b,d,k,f,g){return da&&kb?!0:!1}function v(a,b){var d=a[0]+a[2],k=a[1]+a[3],f=b[1]+b[3];return a[0]>b[0]+b[2]||a[1]>f||da&&hb?!0:!1}function v(a,b){var d=a[0]+a[2],h=a[1]+a[3],f=b[1]+b[3];return a[0]>b[0]+b[2]||a[1]>f||dh.width-c.width-10&&(g=h.width-c.width-10);w>h.height-c.height-10&&(w=h.height-c.height-10)}f.style.left=g+"px";f.style.top=w+"px";b.scale&&(f.style.transform="scale("+b.scale+")")}function B(a){this.points=a;this.nearest=this.selected=-1;this.size=null;this.must_update=!0;this.margin=5}var g=y.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, +a.button)return h.close(),a.preventDefault(),!0},!0);b.scroll_speed||(b.scroll_speed=0.1);f.addEventListener("wheel",d,!0);f.addEventListener("mousewheel",d,!0);this.root=f;if(b.title){var e=document.createElement("div");e.className="litemenu-title";e.innerHTML=b.title;f.appendChild(e)}var e=0,r;for(r in a){var c=a.constructor==Array?a[r]:r;null!=c&&c.constructor!==String&&(c=void 0===c.content?String(c):c.content);this.addItem(c,a[r],b);e++}f.addEventListener("mouseleave",function(a){h.lock||(f.closing_timer&& +clearTimeout(f.closing_timer),f.closing_timer=setTimeout(h.close.bind(h,a),500))});f.addEventListener("mouseenter",function(a){f.closing_timer&&clearTimeout(f.closing_timer)});r=document;b.event&&(r=b.event.target.ownerDocument);r||(r=document);r.fullscreenElement?r.fullscreenElement.appendChild(f):r.body.appendChild(f);e=b.left||0;r=b.top||0;if(b.event){e=b.event.clientX-10;r=b.event.clientY-10;b.title&&(r-=20);b.parentMenu&&(e=b.parentMenu.root.getBoundingClientRect(),e=e.left+e.width);var c=document.body.getBoundingClientRect(), +l=f.getBoundingClientRect();e>c.width-l.width-10&&(e=c.width-l.width-10);r>c.height-l.height-10&&(r=c.height-l.height-10)}f.style.left=e+"px";f.style.top=r+"px";b.scale&&(f.style.transform="scale("+b.scale+")")}function B(a){this.points=a;this.nearest=this.selected=-1;this.size=null;this.must_update=!0;this.margin=5}var e=y.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:1E3,DEFAULT_POSITION:[100, 100],VALID_SHAPES:["default","box","round","card"],BOX_SHAPE:1,ROUND_SHAPE:2,CIRCLE_SHAPE:3,CARD_SHAPE:4,ARROW_SHAPE:5,INPUT:1,OUTPUT:2,EVENT:-1,ACTION:-1,ALWAYS:0,ON_EVENT:1,NEVER:2,ON_TRIGGER:3,UP:1,DOWN:2,LEFT:3,RIGHT:4,CENTER:5,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;g.debug&&console.log("Node registered: "+a);a.split("/");var d=b.name,k=a.lastIndexOf("/");b.category=a.substr(0,k);b.title||(b.title=d);if(b.prototype)for(var f in n.prototype)b.prototype[f]||(b.prototype[f]=n.prototype[f]);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=g.BOX_SHAPE;break;case "round":this._shape=g.ROUND_SHAPE;break;case "circle":this._shape=g.CIRCLE_SHAPE;break;case "card":this._shape=g.CARD_SHAPE;break;default:this._shape=a}},get:function(a){return this._shape},enumerable:!0});(k=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(g.onNodeTypeRegistered)g.onNodeTypeRegistered(a,b);if(k&&g.onNodeTypeReplaced)g.onNodeTypeReplaced(a, -b,k);b.prototype.onPropertyChange&&console.warn("LiteGraph node class "+a+" has onPropertyChange method, it must be called onPropertyChanged with d at the end");if(b.supported_extensions)for(f in b.supported_extensions)(d=b.supported_extensions[f])&&d.constructor===String&&(this.node_types_by_file_extension[d.toLowerCase()]=b)},wrapFunctionAsNode:function(a,b,d,k,f){for(var h=Array(b.length),w="",c=g.getParameterNames(b),e=0;ew&&(w=f.size[0]),c+=f.size[1]+a+g.NODE_TITLE_HEIGHT;b+=w+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||g.ALWAYS;var k=this._nodes_in_order? -this._nodes_in_order:this._nodes;if(k)for(var f=0,h=k.length;f=g.MAX_NUMBER_OF_NODES)throw"LiteGraph: max number of nodes in a graph reached";null==a.id||-1==a.id?a.id=++this.last_node_id:this.last_node_ida.length||(this._pos[0]=a[0],this._pos[1]=a[1])},get:function(){return this._pos}, -enumerable:!0});this.id=-1;this.type=null;this.inputs=[];this.outputs=[];this.connections=[];this.properties={};this.properties_info=[];this.flags={}};n.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]=g.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.outputs.length)){var d=this.outputs[a];if(d&&(d._data=b,this.outputs[a].links))for(d=0;d=this.outputs.length)){var d=this.outputs[a];if(d&&(d.type=b,this.outputs[a].links))for(d=0;d=this.inputs.length||null==this.inputs[a].link)){var d=this.graph.links[this.inputs[a].link];if(!d)return null;if(!b)return d.data;var k=this.graph.getNodeById(d.origin_id);if(!k)return d.data;if(k.updateOutputData)k.updateOutputData(d.origin_slot);else if(k.onExecute)k.onExecute();return d.data}};n.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};n.prototype.getInputDataByName=function(a,b){var d=this.findInputSlot(a);return-1==d?null:this.getInputData(d,b)};n.prototype.isInputConnected=function(a){return this.inputs?a=this.inputs.length)return null;a=this.inputs[a];return a&&null!==a.link?(a=this.graph.links[a.link])?this.graph.getNodeById(a.origin_id):null:null};n.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= -this.outputs.length?null:this.outputs[a]._data};n.prototype.getOutputInfo=function(a){return this.outputs?a=this.outputs.length)return null;a=this.outputs[a];if(!a.links||0==a.links.length)return null;for(var b=[],d=0;da&&this.pos[1]-f-db)return!0;return!1};n.prototype.getSlotInPosition=function(a,b){var d=new Float32Array(2);if(this.inputs)for(var k=0,f=this.inputs.length;k=this.outputs.length)return g.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 g.debug&&console.log("Connect: Error, no slot of name "+d),null}else{if(d===g.EVENT)return null;if(!b.inputs||d>=b.inputs.length)return g.debug&&console.log("Connect: Error, slot number not found"),null}null!=b.inputs[d].link&&b.disconnectInput(d);var k=this.outputs[a];if(b.onConnectInput&&!1===b.onConnectInput(d,k.type,k))return null;var f=b.inputs[d],h=null;if(g.isValidConnection(k.type, -f.type)){h=new m(++this.graph.last_link_id,f.type,this.id,a,b.id,d);this.graph.links[h.id]=h;null==k.links&&(k.links=[]);k.links.push(h.id);b.inputs[d].link=h.id;this.graph&&this.graph._version++;if(this.onConnectionsChange)this.onConnectionsChange(g.OUTPUT,a,!0,h,k);if(b.onConnectionsChange)b.onConnectionsChange(g.INPUT,d,!0,h,f);this.graph&&this.graph.onNodeConnectionChange&&(this.graph.onNodeConnectionChange(g.INPUT,b,d,this,a),this.graph.onNodeConnectionChange(g.OUTPUT,this,a,b,d))}this.setDirtyCanvas(!1, -!0);this.graph.connectionChange(this,h);return h};n.prototype.disconnectOutput=function(a,b){if(a.constructor===String){if(a=this.findOutputSlot(a),-1==a)return g.debug&&console.log("Connect: Error, no slot of name "+a),!1}else if(!this.outputs||a>=this.outputs.length)return g.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 k=0,f=d.links.length;k=this.inputs.length)return g.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 k=this.graph.links[d];if(k){var f=this.graph.getNodeById(k.origin_id);if(!f)return!1;var h=f.outputs[k.origin_slot];if(!h||!h.links||0==h.links.length)return!1;for(var w=0,c=h.links.length;wb&&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&&k>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]/k*(b+0.5),d[1]=a?this.pos[1]-g.NODE_TITLE_HEIGHT:this.pos[1]+this.size[1],d;d[0]=a?this.pos[0]+f:this.pos[0]+this.size[0]+1-f;d[1]=this.pos[1]+(b+0.7)*g.NODE_SLOT_HEIGHT+(this.constructor.slot_start_y||0);return d};n.prototype.alignToGrid=function(){this.pos[0]=g.CANVAS_GRID_SIZE*Math.round(this.pos[0]/g.CANVAS_GRID_SIZE);this.pos[1]=g.CANVAS_GRID_SIZE*Math.round(this.pos[1]/g.CANVAS_GRID_SIZE)};n.prototype.trace= -function(a){this.console||(this.console=[]);this.console.push(a);this.console.length>n.MAX_CONSOLE&&this.console.shift();this.graph.onNodeTrace(this,a)};n.prototype.setDirtyCanvas=function(a,b){this.graph&&this.graph.sendActionToCanvas("setDirty",[a,b])};n.prototype.loadImage=function(a){var b=new Image;b.src=g.node_images_path+a;b.ready=!1;var d=this;b.onload=function(){this.ready=!0;d.setDirtyCanvas(!0)};return b};n.prototype.captureInput=function(a){if(this.graph&&this.graph.list_of_graphcanvas)for(var b= -this.graph.list_of_graphcanvas,d=0;da.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})};l.prototype.configure=function(a){this.title=a.title;this._bounding.set(a.bounding);this.color=a.color;this.font=a.font};l.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}};l.prototype.move= -function(a,b,d){this._pos[0]+=a;this._pos[1]+=b;if(!d)for(d=0;dthis.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);var k=this.convertCanvasToOffset(b),d=[k[0]-d[0],k[1]-d[1]];this.offset[0]+=d[0];this.offset[1]+=d[1];if(this.onredraw)this.onredraw(this)}}};x.prototype.changeDeltaScale=function(a,b){this.changeScale(this.scale*a,b)};x.prototype.reset=function(){this.scale=1;this.offset[0]=0;this.offset[1]=0};y.LGraphCanvas=g.LGraphCanvas=e;e.link_type_colors={"-1":g.EVENT_LINK_COLOR,number:"#AAA", -node:"#DCA"};e.gradients={};e.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()};e.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)))};e.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)};e.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]))}};e.prototype.getCurrentGraph=function(){return this.graph};e.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()}};e.prototype._doNothing=function(a){a.preventDefault();return!1};e.prototype._doReturnTrue=function(a){a.preventDefault();return!0};e.prototype.bindEvents=function(){if(this._events_binded)console.warn("LGraphCanvas: events already binded");else{var a=this.canvas,b=this.getCanvasWindow().document;this._mousedown_callback=this.processMouseDown.bind(this); -this._mousewheel_callback=this.processMouseWheel.bind(this);a.addEventListener("mousedown",this._mousedown_callback,!0);a.addEventListener("mousemove",this._mousemove_callback);a.addEventListener("mousewheel",this._mousewheel_callback,!1);a.addEventListener("contextmenu",this._doNothing);a.addEventListener("DOMMouseScroll",this._mousewheel_callback,!1);a.addEventListener("touchstart",this.touchHandler,!0);a.addEventListener("touchmove",this.touchHandler,!0);a.addEventListener("touchend",this.touchHandler, -!0);a.addEventListener("touchcancel",this.touchHandler,!0);this._key_callback=this.processKey.bind(this);a.addEventListener("keydown",this._key_callback,!0);b.addEventListener("keyup",this._key_callback,!0);this._ondrop_callback=this.processDrop.bind(this);a.addEventListener("dragover",this._doNothing,!1);a.addEventListener("dragend",this._doNothing,!1);a.addEventListener("drop",this._ondrop_callback,!1);a.addEventListener("dragenter",this._doReturnTrue,!1);this._events_binded=!0}};e.prototype.unbindEvents= -function(){if(this._events_binded){var a=this.getCanvasWindow().document;this.canvas.removeEventListener("mousedown",this._mousedown_callback);this.canvas.removeEventListener("mousewheel",this._mousewheel_callback);this.canvas.removeEventListener("DOMMouseScroll",this._mousewheel_callback);this.canvas.removeEventListener("keydown",this._key_callback);a.removeEventListener("keyup",this._key_callback);this.canvas.removeEventListener("contextmenu",this._doNothing);this.canvas.removeEventListener("drop", -this._ondrop_callback);this.canvas.removeEventListener("dragenter",this._doReturnTrue);this.canvas.removeEventListener("touchstart",this.touchHandler);this.canvas.removeEventListener("touchmove",this.touchHandler);this.canvas.removeEventListener("touchend",this.touchHandler);this.canvas.removeEventListener("touchcancel",this.touchHandler);this._ondrop_callback=this._key_callback=this._mousewheel_callback=this._mousedown_callback=null;this._events_binded=!1}else console.warn("LGraphCanvas: no events binded")}; -e.getFileExtension=function(a){var b=a.indexOf("?");-1!=b&&(a=a.substr(0,b));b=a.lastIndexOf(".");return-1==b?"":a.substr(b+1).toLowerCase()};e.prototype.enableWebGL=function(){if(void 0===typeof GL)throw"litegl.js must be included to use a WebGL canvas";if(void 0===typeof enableWebGLCanvas)throw"webglCanvas.js must be included to use this feature";this.gl=this.ctx=enableWebGLCanvas(this.canvas);this.ctx.webgl=!0;this.bgcanvas=this.canvas;this.bgctx=this.gl;this.canvas.webgl_enabled=!0};e.prototype.setDirty= -function(a,b){a&&(this.dirty_canvas=!0);b&&(this.dirty_bgcanvas=!0)};e.prototype.getCanvasWindow=function(){if(!this.canvas)return window;var a=this.canvas.ownerDocument;return a.defaultView||a.parentWindow};e.prototype.startRendering=function(){function a(){this.pause_rendering||this.draw();var b=this.getCanvasWindow();this.is_rendering&&b.requestAnimationFrame(a.bind(this))}this.is_rendering||(this.is_rendering=!0,a.call(this))};e.prototype.stopRendering=function(){this.is_rendering=!1};e.prototype.processMouseDown= -function(a){if(this.graph){this.adjustMouseEvent(a);var b=this.getCanvasWindow();e.active_canvas=this;this.canvas.removeEventListener("mousemove",this._mousemove_callback);b.document.addEventListener("mousemove",this._mousemove_callback,!0);b.document.addEventListener("mouseup",this._mouseup_callback,!0);var d=this.graph.getNodeOnPos(a.canvasX,a.canvasY,this.visible_nodes,5),k=!1,f=300>g.getTime()-this.last_mouseclick;this.canvas_mouse[0]=a.canvasX;this.canvas_mouse[1]=a.canvasY;this.canvas.focus(); -g.closeAllContextMenus(b);if(!this.onMouse||!0!=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,k=!0);var h=!1;if(d&&this.allow_interaction&&!k&&!this.read_only){this.live_mode||d.flags.pinned||this.bringToFront(d);if(!this.connecting_node&&!d.flags.collapsed&&!this.live_mode)if(!k&&!1!==d.resizable&&z(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",k=!0;else{if(d.outputs)for(var w=0,c=d.outputs.length;wh[0]+4||a.canvasYh[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>C([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());f&&!this.read_only&&this.allow_searchbox&&this.showSearchBox(a);h=!0}!k&&h&&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=g.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}}};e.prototype.processMouseMove=function(a){this.autoresize&&this.resize();if(this.graph){e.active_canvas=this;this.adjustMouseEvent(a);var b=[a.localX,a.localY],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);for(var k=this.graph.getNodeOnPos(a.canvasX, -a.canvasY,this.visible_nodes),b=0,f=this.graph._nodes.length;bc[0]+4||a.canvasYc[1]+4)){f=h;break}}f!=this.over_link_center&&(this.over_link_center=f,this.dirty_canvas=!0);this.canvas&&(this.canvas.style.cursor="")}if(this.node_capturing_input&&this.node_capturing_input!=k&&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)k=this.selected_nodes[b], -k.pos[0]+=d[0]/this.ds.scale,k.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)*g.NODE_SLOT_HEIGHT+(this.resizing_node.widgets?this.resizing_node.widgets.length:0)*(g.NODE_WIDGET_HEIGHT+ -4)+4,this.resizing_node.size[1]this.dragging_rectangle[3]?this.dragging_rectangle[1]-f:this.dragging_rectangle[1];this.dragging_rectangle[0]=0>this.dragging_rectangle[2]?this.dragging_rectangle[0]-k:this.dragging_rectangle[0]; -this.dragging_rectangle[1]=h;this.dragging_rectangle[2]=k;this.dragging_rectangle[3]=f;f=[];for(h=0;ha.click_time&&z(a.canvasX,a.canvasY,k.pos[0],k.pos[1]-g.NODE_TITLE_HEIGHT,g.NODE_TITLE_HEIGHT,g.NODE_TITLE_HEIGHT)&&k.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{k=this.graph.getNodeOnPos(a.canvasX,a.canvasY,this.visible_nodes);!k&&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}};e.prototype.processMouseWheel=function(a){if(this.graph&&this.allow_dragcanvas){var b=null!=a.wheelDeltaY?a.wheelDeltaY:-60*a.detail;this.adjustMouseEvent(a);var d=this.ds.scale;0b&&(d*=1/1.1);this.ds.changeScale(d,[a.localX,a.localY]);this.graph.change();a.preventDefault();return!1}};e.prototype.isOverNodeBox= -function(a,b,d){var k=g.NODE_TITLE_HEIGHT;return z(b,d,a.pos[0]+2,a.pos[1]+2-k,k-4,k-4)?!0:!1};e.prototype.isOverNodeInput=function(a,b,d,k){if(a.inputs)for(var f=0,g=a.inputs.length;fd- -this.graph._last_trigger_time)&&this.drawBackCanvas();(this.dirty_canvas||a)&&this.drawFrontCanvas();this.fps=this.render_time?1/this.render_time:0;this.frame+=1}};e.prototype.drawFrontCanvas=function(){this.dirty_canvas=!1;this.ctx||(this.ctx=this.bgcanvas.getContext("2d"));var a=this.ctx;if(a){a.start2D&&a.start2D();var b=this.canvas;a.restore();a.setTransform(1,0,0,1,0,0);this.dirty_area&&(a.save(),a.beginPath(),a.rect(this.dirty_area[0],this.dirty_area[1],this.dirty_area[2],this.dirty_area[3]), -a.clip());this.clear_background&&a.clearRect(0,0,b.width,b.height);this.bgcanvas==this.canvas?this.drawBackCanvas():a.drawImage(this.bgcanvas,0,0);if(this.onRender)this.onRender(b,a);this.show_info&&this.renderInfo(a);if(this.graph){a.save();this.ds.toCanvasContext(a);for(var b=this.computeVisibleNodes(null,this.visible_nodes),d=0;d> ";b.fillText(k+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.5this.ds.scale;if(this.live_mode){if(!a.flags.collapsed&&(b.shadowColor="transparent",a.onDrawForeground))a.onDrawForeground(b,this,this.canvas)}else{var h=this.editor_alpha;b.globalAlpha=h;this.render_shadows&&!f?(b.shadowColor= -g.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||!0!=a.onDrawCollapsed(b,this)){var c=a._shape||g.BOX_SHAPE;q.set(a.size);var e=a.horizontal;if(a.flags.collapsed){b.font=this.inner_text_font;var r=a.getTitle?a.getTitle():a.title;null!=r&&(a._collapsed_width=Math.min(a.size[0],b.measureText(r).width+2*g.NODE_TITLE_HEIGHT),q[0]=a._collapsed_width,q[1]=0)}a.clip_area&& -(b.save(),b.beginPath(),c==g.BOX_SHAPE?b.rect(0,0,q[0],q[1]):c==g.ROUND_SHAPE?b.roundRect(0,0,q[0],q[1],10):c==g.CIRCLE_SHAPE&&b.arc(0.5*q[0],0.5*q[1],0.5*q[0],0,2*Math.PI),b.clip());a.has_errors&&(k="red");this.drawNodeShape(a,b,q,d,k,a.is_selected,a.mouseOver);b.shadowColor="transparent";if(a.onDrawForeground)a.onDrawForeground(b,this,this.canvas);b.textAlign=e?"center":"left";b.font=this.inner_text_font;k=!f;c=this.connecting_output;b.lineWidth=1;var r=0,s=new Float32Array(2);if(!a.flags.collapsed){if(a.inputs)for(d= -0;dthis.ds.scale,p=a._shape||a.constructor.shape||g.ROUND_SHAPE,s=a.constructor.title_mode,l=!0;s==g.TRANSPARENT_TITLE?l=!1:s==g.AUTOHIDE_TITLE&&c&&(l=!0);A[0]=0;A[1]=l?-f:0;A[2]=d[0]+1;A[3]=l?d[1]+f:d[1];c=b.globalAlpha;b.beginPath();p==g.BOX_SHAPE|| -r?b.fillRect(A[0],A[1],A[2],A[3]):p==g.ROUND_SHAPE||p==g.CARD_SHAPE?b.roundRect(A[0],A[1],A[2],A[3],this.round_radius,p==g.CARD_SHAPE?0:this.round_radius):p==g.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,A[2],2));b.shadowColor="transparent";if(a.onDrawBackground)a.onDrawBackground(b,this,this.canvas);if(l||s==g.TRANSPARENT_TITLE){if(a.onDrawTitleBar)a.onDrawTitleBar(b,f,d,this.ds.scale, -k);else if(s!=g.TRANSPARENT_TITLE&&(a.constructor.title_color||this.render_title_colored)){l=a.constructor.title_color||k;a.flags.collapsed&&(b.shadowColor=g.DEFAULT_SHADOW_COLOR);if(this.use_gradients){var u=e.gradients[l];u||(u=e.gradients[l]=b.createLinearGradient(0,0,400,0),u.addColorStop(0,l),u.addColorStop(1,"#000"));b.fillStyle=u}else b.fillStyle=l;b.beginPath();p==g.BOX_SHAPE||r?b.rect(0,-f,d[0]+1,f):p!=g.ROUND_SHAPE&&p!=g.CARD_SHAPE||b.roundRect(0,-f,d[0]+1,f,this.round_radius,a.flags.collapsed? -this.round_radius:0);b.fill();b.shadowColor="transparent"}if(a.onDrawTitleBox)a.onDrawTitleBox(b,f,d,this.ds.scale);else p==g.ROUND_SHAPE||p==g.CIRCLE_SHAPE||p==g.CARD_SHAPE?(r&&(b.fillStyle="black",b.beginPath(),b.arc(0.5*f,-0.5*f,6,0,2*Math.PI),b.fill()),b.fillStyle=a.boxcolor||g.NODE_DEFAULT_BOXCOLOR,r?b.fillRect(0.5*f-5,-0.5*f-5,10,10):(b.beginPath(),b.arc(0.5*f,-0.5*f,5,0,2*Math.PI),b.fill())):(r&&(b.fillStyle="black",b.fillRect(0.5*(f-10)-1,-0.5*(f+10)-1,12,12)),b.fillStyle=a.boxcolor||g.NODE_DEFAULT_BOXCOLOR, -b.fillRect(0.5*(f-10),-0.5*(f+10),10,10));b.globalAlpha=c;if(a.onDrawTitleText)a.onDrawTitleText(b,f,d,this.ds.scale,this.title_text_font,h);!r&&(b.font=this.title_text_font,r=a.getTitle())&&(b.fillStyle=h?"white":a.constructor.title_text_color||this.node_title_color,a.flags.collapsed?(b.textAlign="center",c=b.measureText(r),b.fillText(r,f+0.5*c.width,g.NODE_TITLE_TEXT_Y-f),b.textAlign="left"):(b.textAlign="left",b.fillText(r,f,g.NODE_TITLE_TEXT_Y-f)));if(a.onDrawTitle)a.onDrawTitle(b)}if(h){if(a.onBounding)a.onBounding(A); -s==g.TRANSPARENT_TITLE&&(A[1]-=f,A[3]+=f);b.lineWidth=1;b.globalAlpha=0.8;b.beginPath();p==g.BOX_SHAPE?b.rect(-6+A[0],-6+A[1],12+A[2],12+A[3]):p==g.ROUND_SHAPE||p==g.CARD_SHAPE&&a.flags.collapsed?b.roundRect(-6+A[0],-6+A[1],12+A[2],12+A[3],2*this.round_radius):p==g.CARD_SHAPE?b.roundRect(-6+A[0],-6+A[1],12+A[2],12+A[3],2*this.round_radius,2):p==g.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=k;b.globalAlpha=1}};var s=new Float32Array(4), -p=new Float32Array(4),t=new Float32Array(2),h=new Float32Array(2);e.prototype.drawConnections=function(a){var b=g.getTime(),d=this.visible_area;s[0]=d[0]-20;s[1]=d[1]-20;s[2]=d[2]+40;s[3]=d[3]+40;a.lineWidth=this.connections_width;a.fillStyle="#AAA";a.strokeStyle="#AAA";a.globalAlpha=this.editor_alpha;for(var d=this.graph._nodes,k=0,f=d.length;kp[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(v(p,s)){var n=r.outputs[l],l=c.inputs[w];if(n&&l&&(r=n.dir||(r.horizontal?g.DOWN:g.RIGHT),l=l.dir||(c.horizontal?g.UP:g.LEFT),this.renderLink(a,q,u,e,!1,0,null,r,l),e&&e._last_time&&1E3>b-e._last_time)){var n= -2-0.002*(b-e._last_time),J=a.globalAlpha;a.globalAlpha=J*n;this.renderLink(a,q,u,e,!0,n,"white",r,l);a.globalAlpha=J}}}}}}a.globalAlpha=1};e.prototype.renderLink=function(a,b,d,k,f,h,c,r,p,s){k&&this.visible_links.push(k);!c&&k&&(c=k.color||e.link_type_colors[k.type]);c||(c=this.default_link_color);null!=k&&this.highlighted_links[k.id]&&(c="#FFF");r=r||g.RIGHT;p=p||g.LEFT;var l=C(b,d);this.render_connections_border&&0.6b[1]?0:Math.PI,a.save(),a.translate(u[0],u[1]),a.rotate(q),a.beginPath(),a.moveTo(-5,-3),a.lineTo(0,7),a.lineTo(5,-3),a.fill(),a.restore(),a.save(),a.translate(s[0],s[1]),a.rotate(n),a.beginPath(),a.moveTo(-5,-3),a.lineTo(0,7),a.lineTo(5,-3),a.fill(),a.restore()),a.beginPath(),a.arc(f[0],f[1],5,0,2*Math.PI),a.fill());if(h)for(a.fillStyle= -c,u=0;5>u;++u)h=(0.001*g.getTime()+0.2*u)%1,f=this.computeConnectionPoint(b,d,h,r,p),a.beginPath(),a.arc(f[0],f[1],5,0,2*Math.PI),a.fill()};e.prototype.computeConnectionPoint=function(a,b,d,k,f){k=k||g.RIGHT;f=f||g.LEFT;var h=C(a,b),c=[a[0],a[1]],e=[b[0],b[1]];switch(k){case g.LEFT:c[0]+=-0.25*h;break;case g.RIGHT:c[0]+=0.25*h;break;case g.UP:c[1]+=-0.25*h;break;case g.DOWN:c[1]+=0.25*h}switch(f){case g.LEFT:e[0]+=-0.25*h;break;case g.RIGHT:e[0]+=0.25*h;break;case g.UP:e[1]+=-0.25*h;break;case g.DOWN:e[1]+= -0.25*h}k=(1-d)*(1-d)*(1-d);f=3*(1-d)*(1-d)*d;h=3*(1-d)*d*d;d*=d*d;return[k*a[0]+f*c[0]+h*e[0]+d*b[0],k*a[1]+f*c[1]+h*e[1]+d*b[1]]};e.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;du.last_y&&cu.options.max&&(u.value=u.options.max);else if("mousedown"==d.type)if((c=u.options.values)&&c.constructor===Function&&(c=u.options.values(u,a)),h=40>h?-1:h>e-40?1:0,"number"==u.type)u.value+=0.1*h*(u.options.step||1),null!=u.options.min&&u.valueu.options.max&&(u.value=u.options.max);else if(h)p=c.indexOf(u.value)+h,p>=c.length&&(p=0),0>p&&(p=c.length-1),u.value=c[p];else{new g.ContextMenu(c,{scale:Math.max(1,this.ds.scale),event:d,className:"dark",callback:q.bind(u)},p);var q=function(a,b,d){this.value=a;f(this,a);r.dirty_canvas=!0;return!1}}k!=u.value&&setTimeout(function(){f(this,this.value)}.bind(u),20);this.dirty_canvas=!0;break;case "toggle":"mousedown"==d.type&&(u.value=!u.value,setTimeout(function(){f(u, -u.value)},20));break;case "string":case "text":"mousedown"==d.type&&this.prompt("Value",u.value,function(a){this.value=a;f(this,a)}.bind(u),d);break;default:u.mouse&&u.mouse(ctx,d,[h,c],a)}return u}}return null};e.prototype.drawGroups=function(a,b){if(this.graph){var d=this.graph._groups;b.save();b.globalAlpha=0.5*this.editor_alpha;for(var k=0;k -d&&0.01>b.editor_alpha&&(clearInterval(k),1>d&&(b.live_mode=!0));1"+p+""+a+"",value:p});if(r.length)return new g.ContextMenu(r,{event:d,callback:h,parentMenu:k,allow_html:!0,node:f},b),!1}};e.decodeHTML=function(a){var b= -document.createElement("div");b.innerText=a;return b.innerHTML};e.onResizeNode=function(a,b,d,k,f){f&&(f.size=f.computeSize(),f.setDirtyCanvas(!0,!0))};e.prototype.showLinkMenu=function(a,b){var d=this;console.log(a);var k=new g.ContextMenu(["Add Node",null,"Delete"],{event:b,title:null!=a.data?a.data.constructor.name:null,callback:function(b,g,h){switch(b){case "Add Node":e.onMenuAdd(null,null,h,k,function(b){console.log("node autoconnect");var f=d.graph.getNodeById(a.origin_id),k=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==k.inputs[0].type&&(f.connect(a.origin_slot,b,0),b.connect(0,k,a.target_slot),b.pos[0]-=0.5*b.size[0])});break;case "Delete":d.graph.removeLink(a.id)}}});return!1};e.onShowPropertyEditor=function(a,b,d,k,f){function h(){var b=r.value;"Number"==a.type?b=Number(b):"Boolean"==a.type&&(b=Boolean(b));f[g]=b;c.parentNode&&c.parentNode.removeChild(c);f.setDirtyCanvas(!0,!0)}var g=a.property|| -"title";b=f[g];var c=document.createElement("div");c.className="graphdialog";c.innerHTML="";c.querySelector(".name").innerText=g;var r=c.querySelector("input");r&&(r.value=b,r.addEventListener("blur",function(a){this.focus()}),r.addEventListener("keydown",function(a){13==a.keyCode&&(h(),a.preventDefault(),a.stopPropagation())}));b=e.active_canvas.canvas;d=b.getBoundingClientRect();var p=k=-20;d&&(k-=d.left,p-= -d.top);event?(c.style.left=event.clientX+k+"px",c.style.top=event.clientY+p+"px"):(c.style.left=0.5*b.width+k+"px",c.style.top=0.5*b.height+p+"px");c.querySelector("button").addEventListener("click",h);b.parentNode.appendChild(c)};e.prototype.prompt=function(a,b,d,k){var f=this;a=a||"";var g=!1,h=document.createElement("div");h.className="graphdialog rounded";h.innerHTML=" ";h.close=function(){f.prompt_box= -null;h.parentNode&&h.parentNode.removeChild(h)};1e.search_limit)break}}p=null;if(Array.prototype.filter)p=Object.keys(g.registered_node_types).filter(w);else for(c in p= -[],g.registered_node_types)w(c)&&p.push(c);for(c=0;ce.search_limit);c++);var w=function(a){var b=g.registered_node_types[a];return r&&b.filter!=r?!1:-1!==a.toLowerCase().indexOf(d)}}}var f=this,h=e.active_canvas,c=h.canvas,r=c.ownerDocument||document,p=document.createElement("div");p.className="litegraph litesearchbox graphdialog rounded";p.innerHTML="Search
"; -p.close=function(){f.search_box=null;r.body.focus();r.body.style.overflow="";setTimeout(function(){f.canvas.focus()},20);p.parentNode&&p.parentNode.removeChild(p)};var s=null;1c.height-200&&(q.style.maxHeight=c.height-a.layerY-20+"px");t.focus();return p};e.prototype.showEditPropertyValue=function(a,b,d){function k(){f(u.value)}function f(d){"number"==typeof a.properties[b]&&(d=Number(d));if("array"==h||"object"==h)d=JSON.parse(d);a.properties[b]=d;a._graph&&a._graph._version++;if(a.onPropertyChanged)a.onPropertyChanged(b, -d);e.close();a.setDirtyCanvas(!0,!0)}if(a&&void 0!==a.properties[b]){d=d||{};var h="string";null!==a.properties[b]&&(h=typeof a.properties[b]);var g=null;a.getPropertyInfo&&(g=a.getPropertyInfo(b));if(a.properties_info)for(var c=0;c";else if("enum"==h&&g.values){r= -""}else if("boolean"==h)r="";else{console.warn("unknown type: "+h);return}var e=this.createDialog(""+b+""+r+"",d);if("enum"==h&&g.values){var u=e.querySelector("select"); -u.addEventListener("change",function(a){f(a.target.value)})}else if("boolean"==h)(u=e.querySelector("input"))&&u.addEventListener("click",function(a){f(!!u.checked)});else if(u=e.querySelector("input"))u.addEventListener("blur",function(a){this.focus()}),p=void 0!==a.properties[b]?a.properties[b]:"",p=JSON.stringify(p),u.value=p,u.addEventListener("keydown",function(a){13==a.keyCode&&(k(),a.preventDefault(),a.stopPropagation())});e.querySelector("button").addEventListener("click",k)}};e.prototype.createDialog= -function(a,b){b=b||{};var d=document.createElement("div");d.className="graphdialog";d.innerHTML=a;var k=this.canvas.getBoundingClientRect(),f=-20,h=-20;k&&(f-=k.left,h-=k.top);b.position?(f+=b.position[0],h+=b.position[1]):b.event?(f+=b.event.clientX,h+=b.event.clientY):(f+=0.5*this.canvas.width,h+=0.5*this.canvas.height);d.style.left=f+"px";d.style.top=h+"px";this.canvas.parentNode.appendChild(d);d.close=function(){this.parentNode&&this.parentNode.removeChild(this)};return d};e.onMenuNodeCollapse= -function(a,b,d,k,f){f.collapse()};e.onMenuNodePin=function(a,b,d,k,f){f.pin()};e.onMenuNodeMode=function(a,b,d,k,f){new g.ContextMenu(["Always","On Event","On Trigger","Never"],{event:d,callback:function(a){if(f)switch(a){case "On Event":f.mode=g.ON_EVENT;break;case "On Trigger":f.mode=g.ON_TRIGGER;break;case "Never":f.mode=g.NEVER;break;default:f.mode=g.ALWAYS}},parentMenu:k,node:f});return!1};e.onMenuNodeColors=function(a,b,d,k,f){if(!f)throw"no node for color";b=[];b.push({value:null,content:"No color"}); -for(var h in e.node_colors)a=e.node_colors[h],a={value:h,content:""+h+""},b.push(a);new g.ContextMenu(b,{event:d,callback:function(a){f&&((a=a.value?e.node_colors[a.value]:null)?f.constructor===g.LGraphGroup?f.color=a.groupcolor:(f.color=a.color,f.bgcolor=a.bgcolor):(delete f.color,delete f.bgcolor),f.setDirtyCanvas(!0,!0))},parentMenu:k,node:f});return!1};e.onMenuNodeShapes= -function(a,b,d,k,f){if(!f)throw"no node passed";new g.ContextMenu(g.VALID_SHAPES,{event:d,callback:function(a){f&&(f.shape=a,f.setDirtyCanvas(!0))},parentMenu:k,node:f});return!1};e.onMenuNodeRemove=function(a,b,d,k,f){if(!f)throw"no node passed";!1!==f.removable&&(f.graph.remove(f),f.setDirtyCanvas(!0,!0))};e.onMenuNodeClone=function(a,b,d,k,f){!1!=f.clonable&&(a=f.clone())&&(a.pos=[f.pos[0]+5,f.pos[1]+5],f.graph.add(a),f.setDirtyCanvas(!0,!0))};e.node_colors={red:{color:"#322",bgcolor:"#533",groupcolor:"#A88"}, -brown:{color:"#332922",bgcolor:"#593930",groupcolor:"#b06634"},green:{color:"#232",bgcolor:"#353",groupcolor:"#8A8"},blue:{color:"#223",bgcolor:"#335",groupcolor:"#88A"},pale_blue:{color:"#2a363b",bgcolor:"#3f5159",groupcolor:"#3f789e"},cyan:{color:"#233",bgcolor:"#355",groupcolor:"#8AA"},purple:{color:"#323",bgcolor:"#535",groupcolor:"#a1309b"},yellow:{color:"#432",bgcolor:"#653",groupcolor:"#b58b2a"},black:{color:"#222",bgcolor:"#000",groupcolor:"#444"}};e.prototype.getCanvasMenuOptions=function(){var a= -null;this.getMenuOptions?a=this.getMenuOptions():(a=[{content:"Add Node",has_submenu:!0,callback:e.onMenuAdd},{content:"Add Group",callback:e.onGroupAdd}],this._graph_stack&&0Name",f),g=c.querySelector("input");g&&h&&(g.value=h.label||"");c.querySelector("button").addEventListener("click",function(a){g.value&& -(h&&(h.label=g.value),d.setDirty(!0));c.close()})}},extra:a};a&&(h.title=a.type);var c=null;a&&(c=a.getSlotInPosition(b.canvasX,b.canvasY),e.active_node=a);if(c){f=[];c&&c.output&&c.output.links&&c.output.links.length&&f.push({content:"Disconnect Links",slot:c});var r=c.input||c.output;f.push(r.locked?"Cannot remove":{content:"Remove Slot",slot:c});f.push(r.nameLocked?"Cannot rename":{content:"Rename Slot",slot:c});h.title=(c.input?c.input.type:c.output.type)||"*";c.input&&c.input.type==g.ACTION&& -(h.title="Action");c.output&&c.output.type==g.EVENT&&(h.title="Event")}else a?f=this.getNodeMenuOptions(a):(f=this.getCanvasMenuOptions(),(c=this.graph.getGroupOnPos(b.canvasX,b.canvasY))&&f.push(null,{content:"Edit Group",has_submenu:!0,submenu:{title:"Group",extra:c,options:this.getGroupMenuOptions(c)}}));f&&new g.ContextMenu(f,h,k)};this.CanvasRenderingContext2D&&(CanvasRenderingContext2D.prototype.roundRect=function(a,b,d,k,f,h){void 0===f&&(f=5);void 0===h&&(h=f);this.moveTo(a+f,b);this.lineTo(a+ -d-f,b);this.quadraticCurveTo(a+d,b,a+d,b+f);this.lineTo(a+d,b+k-h);this.quadraticCurveTo(a+d,b+k,a+d-h,b+k);this.lineTo(a+h,b+k);this.quadraticCurveTo(a,b+k,a,b+k-h);this.lineTo(a,b+f);this.quadraticCurveTo(a,b,a+f,b)});g.compareObjects=function(a,b){for(var d in a)if(a[d]!=b[d])return!1;return!0};g.distance=C;g.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")+")"};g.isInsideRectangle= -z;g.growBounding=function(a,b,d){ba[2]&&(a[2]=b);da[3]&&(a[3]=d)};g.isInsideBounding=function(a,b){return a[0]b[1][0]||a[1]>b[1][1]?!1:!0};g.overlapBounding=v;g.hex2num=function(a){"#"==a.charAt(0)&&(a=a.slice(1));a=a.toUpperCase();for(var b=Array(3),d=0,k,f,h=0;6>h;h+=2)k="0123456789ABCDEF".indexOf(a.charAt(h)),f="0123456789ABCDEF".indexOf(a.charAt(h+1)),b[d]=16*k+f,d++;return b};g.num2hex=function(a){for(var b="#",d,k,f=0;3>f;f++)d=a[f]/ -16,k=a[f]%16,b+="0123456789ABCDEF".charAt(d)+"0123456789ABCDEF".charAt(k);return b};D.prototype.addItem=function(a,b,d){function k(a){var b=this.value;b&&b.has_submenu&&f.call(this,a)}function f(a){var b=this.value,f=!0;h.current_submenu&&h.current_submenu.close(a);if(d.callback){var k=d.callback.call(this,b,d,a,h,d.node);!0===k&&(f=!1)}if(b&&(b.callback&&!d.ignore_item_callbacks&&!0!==b.disabled&&(k=b.callback.call(this,b,d,a,h,d.extra),!0===k&&(f=!1)),b.submenu)){if(!b.submenu.options)throw"ContextMenu submenu needs options"; -new h.constructor(b.submenu.options,{callback:b.submenu.callback,event:a,parentMenu:h,ignore_item_callbacks:b.submenu.ignore_item_callbacks,title:b.submenu.title,extra:b.submenu.extra,autoopen:d.autoopen});f=!1}f&&!h.lock&&h.close()}var h=this;d=d||{};var c=document.createElement("div");c.className="litemenu-entry submenu";var g=!1;if(null===b)c.classList.add("separator");else{c.innerHTML=b&&b.title?b.title:a;if(c.value=b)b.disabled&&(g=!0,c.classList.add("disabled")),(b.submenu||b.has_submenu)&& -c.classList.add("has_submenu");"function"==typeof b?(c.dataset.value=a,c.onclick_callback=b):c.dataset.value=b;b.className&&(c.className+=" "+b.className)}this.root.appendChild(c);g||c.addEventListener("click",f);d.autoopen&&c.addEventListener("mouseenter",k);return c};D.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&&!D.isCursorOverElement(a, -this.parentMenu.root)&&D.trigger(this.parentMenu.root,"mouseleave",a));this.current_submenu&&this.current_submenu.close(a,!0);this.root.closing_timer&&clearTimeout(this.root.closing_timer)};D.trigger=function(a,b,d,k){var f=document.createEvent("CustomEvent");f.initCustomEvent(b,!0,!0,d);f.srcElement=k;a.dispatchEvent?a.dispatchEvent(f):a.__events&&a.__events.dispatchEvent(f);return f};D.prototype.getTopMenu=function(){return this.options.parentMenu?this.options.parentMenu.getTopMenu():this};D.prototype.getFirstEvent= -function(){return this.options.parentMenu?this.options.parentMenu.getFirstEvent():this.options.event};D.isCursorOverElement=function(a,b){var d=a.clientX,k=a.clientY,f=b.getBoundingClientRect();return f?k>f.top&&kf.left&&dMath.abs(d))return k[1];d=(a-k[0])/d;return k[1]*(1-d)+f[1]*d}}return 0}};B.prototype.draw=function(a,b,d,k,f,h){if(d=this.points){this.size=b;var c=b[0]-2*this.margin;b=b[1]-2*this.margin;f=f||"#666";a.save();a.translate(this.margin,this.margin);k&&(a.fillStyle="#111",a.fillRect(0,0,c,b),a.fillStyle="#222",a.fillRect(0.5*c,0,1,b),a.strokeStyle="#333",a.strokeRect(0,0,c,b));a.strokeStyle=f;h&&(a.globalAlpha=0.5);a.beginPath();for(k=0;ka[1])){var k=this.size[0]-2*this.margin,f=this.size[1]-2*this.margin,h=a[0]-this.margin,c=a[1]-this.margin;this.selected=this.getCloserPoint([h,c],30/b.ds.scale);-1==this.selected&&(k=[h/k,1-c/f],d.push(k),d.sort(function(a,b){return a[0]- -b[0]}),this.selected=d.indexOf(k),this.must_update=!0);if(-1!=this.selected)return!0}};B.prototype.onMouseMove=function(a,b){var d=this.points;if(d){var k=this.selected;if(!(0>k)){var f=(a[0]-this.margin)/(this.size[0]-2*this.margin),h=(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);var c=d[k];if(c){var g=0==k||k==d.length-1;!g&&(-10>a[0]||a[0]>this.size[0]+10||-10>a[1]||a[1]>this.size[1]+10)?(d.splice(k,1),this.selected= --1):(c[0]=g?0==k?0:1:Math.clamp(f,0,1),c[1]=1-Math.clamp(h,0,1),d.sort(function(a,b){return a[0]-b[0]}),this.selected=d.indexOf(c),this.must_update=!0)}}}};B.prototype.onMouseUp=function(a,b){this.selected=-1;return!1};B.prototype.getCloserPoint=function(a,b){var d=this.points;if(!d)return-1;b=b||30;for(var k=this.size[0]-2*this.margin,f=this.size[1]-2*this.margin,h=d.length,c=[0,0],g=1E6,r=-1,p=0;pg||e>b||(r=p,g=e)}return r};g.CurveEditor= -B;g.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:dthis.size[0]-t.NODE_TITLE_HEIGHT&&0>a[1]){var d=this;setTimeout(function(){b.openSubgraph(d.subgraph)}, -10)}};m.prototype.onAction=function(h,a){this.subgraph.onAction(h,a)};m.prototype.onExecute=function(){if(this.enabled=this.getInputOrProperty("enabled")){if(this.inputs)for(var h=0;h=g?this.trigger(null,e):this._pending.push([g,e])};e.prototype.onExecute=function(){var c=1E3*this.graph.elapsed_time;this.isInputConnected(1)&&(this.properties.time_in_ms=this.getInputData(1));for(var e=0;er&&(r=f.size[0]),l+=f.size[1]+a+e.NODE_TITLE_HEIGHT;b+=r+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 h=this._nodes_in_order?this._nodes_in_order:this._nodes;if(h)for(var f=0,c=h.length;f=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_ida.length||(this._pos[0]=a[0],this._pos[1]=a[1])},get:function(){return this._pos},enumerable:!0});this.id=-1;this.type=null;this.inputs=[];this.outputs=[];this.connections=[];this.properties={};this.properties_info=[];this.flags={}};n.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.outputs.length)){var d=this.outputs[a];if(d&&(d._data=b,this.outputs[a].links))for(d=0;d=this.outputs.length)){var d=this.outputs[a];if(d&&(d.type=b,this.outputs[a].links))for(d=0;d=this.inputs.length||null==this.inputs[a].link)){var d=this.graph.links[this.inputs[a].link];if(!d)return null;if(!b)return d.data; +var h=this.graph.getNodeById(d.origin_id);if(!h)return d.data;if(h.updateOutputData)h.updateOutputData(d.origin_slot);else if(h.onExecute)h.onExecute();return d.data}};n.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};n.prototype.getInputDataByName=function(a,b){var d=this.findInputSlot(a); +return-1==d?null:this.getInputData(d,b)};n.prototype.isInputConnected=function(a){return this.inputs?a=this.inputs.length)return null;a=this.inputs[a];return a&&null!==a.link?(a=this.graph.links[a.link])?this.graph.getNodeById(a.origin_id):null:null};n.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=this.outputs.length?null:this.outputs[a]._data};n.prototype.getOutputInfo=function(a){return this.outputs?a=this.outputs.length)return null;a=this.outputs[a];if(!a.links||0==a.links.length)return null;for(var b=[],d=0;da&&this.pos[1]-f-db)return!0;return!1};n.prototype.getSlotInPosition=function(a,b){var d=new Float32Array(2); +if(this.inputs)for(var h=0,f=this.inputs.length;h=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 h=this.outputs[a];if(b.onConnectInput&&!1===b.onConnectInput(d,h.type,h))return null;var f=b.inputs[d],c=null;if(e.isValidConnection(h.type,f.type)){c=new m(++this.graph.last_link_id,f.type,this.id,a,b.id,d);this.graph.links[c.id]=c;null==h.links&&(h.links=[]);h.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,h);if(b.onConnectionsChange)b.onConnectionsChange(e.INPUT,d,!0,c,f);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};n.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 h=0,f=d.links.length;h=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 h=this.graph.links[d];if(h){var f=this.graph.getNodeById(h.origin_id);if(!f)return!1;var c=f.outputs[h.origin_slot];if(!c||!c.links||0==c.links.length)return!1;for(var r=0,l=c.links.length;r< +l;r++)if(c.links[r]==d){c.links.splice(r,1);break}delete this.graph.links[d];this.graph&&this.graph._version++;if(this.onConnectionsChange)this.onConnectionsChange(e.INPUT,a,!1,h,b);if(f.onConnectionsChange)f.onConnectionsChange(e.OUTPUT,r,!1,h,c);this.graph&&this.graph.onNodeConnectionChange&&(this.graph.onNodeConnectionChange(e.OUTPUT,f,r),this.graph.onNodeConnectionChange(e.INPUT,this,a))}this.setDirtyCanvas(!1,!0);this.graph.connectionChange(this);return!0};n.prototype.getConnectionPos=function(a, +b,d){d=d||new Float32Array(2);var h=0;a&&this.inputs&&(h=this.inputs.length);!a&&this.outputs&&(h=this.outputs.length);var f=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&& +h>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&&h>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]/h*(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]+f:this.pos[0]+this.size[0]+1-f;d[1]=this.pos[1]+(b+0.7)*e.NODE_SLOT_HEIGHT+(this.constructor.slot_start_y||0);return d}; +n.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)};n.prototype.trace=function(a){this.console||(this.console=[]);this.console.push(a);this.console.length>n.MAX_CONSOLE&&this.console.shift();this.graph.onNodeTrace(this,a)};n.prototype.setDirtyCanvas=function(a,b){this.graph&&this.graph.sendActionToCanvas("setDirty",[a,b])};n.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};n.prototype.captureInput=function(a){if(this.graph&&this.graph.list_of_graphcanvas)for(var b=this.graph.list_of_graphcanvas,d=0;da.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})};k.prototype.configure=function(a){this.title=a.title;this._bounding.set(a.bounding);this.color=a.color; +this.font=a.font};k.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}};k.prototype.move=function(a,b,d){this._pos[0]+=a;this._pos[1]+=b;if(!d)for(d=0;dthis.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);var h=this.convertCanvasToOffset(b),d=[h[0]-d[0],h[1]-d[1]];this.offset[0]+=d[0];this.offset[1]+=d[1];if(this.onredraw)this.onredraw(this)}}}; +x.prototype.changeDeltaScale=function(a,b){this.changeScale(this.scale*a,b)};x.prototype.reset=function(){this.scale=1;this.offset[0]=0;this.offset[1]=0};y.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(){if(void 0===typeof GL)throw"litegl.js must be included to use a WebGL canvas"; +if(void 0===typeof enableWebGLCanvas)throw"webglCanvas.js must be included to use this feature";this.gl=this.ctx=enableWebGLCanvas(this.canvas);this.ctx.webgl=!0;this.bgcanvas=this.canvas;this.bgctx=this.gl;this.canvas.webgl_enabled=!0};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),h=!1,f=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||!0!=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,h=!0);var c= +!1;if(d&&this.allow_interaction&&!h&&!this.read_only){this.live_mode||d.flags.pinned||this.bringToFront(d);if(!this.connecting_node&&!d.flags.collapsed&&!this.live_mode)if(!h&&!1!==d.resizable&&z(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",h=!0;else{if(d.outputs)for(var r=0,l=d.outputs.length;rc[0]+4||a.canvasYc[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>C([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());f&&!this.read_only&&this.allow_searchbox&&this.showSearchBox(a); +c=!0}!h&&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);for(var h=this.graph.getNodeOnPos(a.canvasX,a.canvasY,this.visible_nodes),b=0,f=this.graph._nodes.length;br[0]+4||a.canvasYr[1]+4)){f=c;break}}f!=this.over_link_center&&(this.over_link_center=f,this.dirty_canvas=!0);this.canvas&&(this.canvas.style.cursor="")}if(this.node_capturing_input&&this.node_capturing_input!=h&&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)h=this.selected_nodes[b],h.pos[0]+=d[0]/this.ds.scale,h.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]this.dragging_rectangle[3]?this.dragging_rectangle[1]-f:this.dragging_rectangle[1];this.dragging_rectangle[0]=0>this.dragging_rectangle[2]?this.dragging_rectangle[0]-h:this.dragging_rectangle[0];this.dragging_rectangle[1]=c;this.dragging_rectangle[2]=h;this.dragging_rectangle[3]=f;f=[];for(c=0;ca.click_time&&z(a.canvasX,a.canvasY,h.pos[0],h.pos[1]-e.NODE_TITLE_HEIGHT,e.NODE_TITLE_HEIGHT,e.NODE_TITLE_HEIGHT)&&h.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{h=this.graph.getNodeOnPos(a.canvasX,a.canvasY,this.visible_nodes);!h&&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;0b&&(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 h=e.NODE_TITLE_HEIGHT;return z(b,d,a.pos[0]+2,a.pos[1]+2-h,h-4,h-4)?!0:!1};g.prototype.isOverNodeInput=function(a,b,d,h){if(a.inputs)for(var f=0,e=a.inputs.length;fd-this.graph._last_trigger_time)&&this.drawBackCanvas();(this.dirty_canvas||a)&&this.drawFrontCanvas();this.fps=this.render_time?1/this.render_time:0;this.frame+=1}};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);for(var b=this.computeVisibleNodes(null,this.visible_nodes),d=0;d> ";b.fillText(h+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.5this.ds.scale;if(this.live_mode){if(!a.flags.collapsed&&(b.shadowColor="transparent",a.onDrawForeground))a.onDrawForeground(b,this,this.canvas)}else{var c= +this.editor_alpha;b.globalAlpha=c;this.render_shadows&&!f?(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||!0!=a.onDrawCollapsed(b,this)){var r=a._shape||e.BOX_SHAPE;t.set(a.size);var l=a.horizontal;if(a.flags.collapsed){b.font=this.inner_text_font;var g=a.getTitle?a.getTitle():a.title;null!=g&&(a._collapsed_width=Math.min(a.size[0],b.measureText(g).width+ +2*e.NODE_TITLE_HEIGHT),t[0]=a._collapsed_width,t[1]=0)}a.clip_area&&(b.save(),b.beginPath(),r==e.BOX_SHAPE?b.rect(0,0,t[0],t[1]):r==e.ROUND_SHAPE?b.roundRect(0,0,t[0],t[1],10):r==e.CIRCLE_SHAPE&&b.arc(0.5*t[0],0.5*t[1],0.5*t[0],0,2*Math.PI),b.clip());a.has_errors&&(h="red");this.drawNodeShape(a,b,t,d,h,a.is_selected,a.mouseOver);b.shadowColor="transparent";if(a.onDrawForeground)a.onDrawForeground(b,this,this.canvas);b.textAlign=l?"center":"left";b.font=this.inner_text_font;h=!f;r=this.connecting_output; +b.lineWidth=1;var g=0,q=new Float32Array(2);if(!a.flags.collapsed){if(a.inputs)for(d=0;dthis.ds.scale,p=a._shape||a.constructor.shape||e.ROUND_SHAPE,q=a.constructor.title_mode,s=!0;q==e.TRANSPARENT_TITLE?s=!1:q==e.AUTOHIDE_TITLE&&r&&(s= +!0);A[0]=0;A[1]=s?-f:0;A[2]=d[0]+1;A[3]=s?d[1]+f:d[1];r=b.globalAlpha;b.beginPath();p==e.BOX_SHAPE||l?b.fillRect(A[0],A[1],A[2],A[3]):p==e.ROUND_SHAPE||p==e.CARD_SHAPE?b.roundRect(A[0],A[1],A[2],A[3],this.round_radius,p==e.CARD_SHAPE?0:this.round_radius):p==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,A[2],2));b.shadowColor="transparent";if(a.onDrawBackground)a.onDrawBackground(b, +this,this.canvas);if(s||q==e.TRANSPARENT_TITLE){if(a.onDrawTitleBar)a.onDrawTitleBar(b,f,d,this.ds.scale,h);else if(q!=e.TRANSPARENT_TITLE&&(a.constructor.title_color||this.render_title_colored)){s=a.constructor.title_color||h;a.flags.collapsed&&(b.shadowColor=e.DEFAULT_SHADOW_COLOR);if(this.use_gradients){var u=g.gradients[s];u||(u=g.gradients[s]=b.createLinearGradient(0,0,400,0),u.addColorStop(0,s),u.addColorStop(1,"#000"));b.fillStyle=u}else b.fillStyle=s;b.beginPath();p==e.BOX_SHAPE||l?b.rect(0, +-f,d[0]+1,f):p!=e.ROUND_SHAPE&&p!=e.CARD_SHAPE||b.roundRect(0,-f,d[0]+1,f,this.round_radius,a.flags.collapsed?this.round_radius:0);b.fill();b.shadowColor="transparent"}if(a.onDrawTitleBox)a.onDrawTitleBox(b,f,d,this.ds.scale);else p==e.ROUND_SHAPE||p==e.CIRCLE_SHAPE||p==e.CARD_SHAPE?(l&&(b.fillStyle="black",b.beginPath(),b.arc(0.5*f,-0.5*f,6,0,2*Math.PI),b.fill()),b.fillStyle=a.boxcolor||e.NODE_DEFAULT_BOXCOLOR,l?b.fillRect(0.5*f-5,-0.5*f-5,10,10):(b.beginPath(),b.arc(0.5*f,-0.5*f,5,0,2*Math.PI), +b.fill())):(l&&(b.fillStyle="black",b.fillRect(0.5*(f-10)-1,-0.5*(f+10)-1,12,12)),b.fillStyle=a.boxcolor||e.NODE_DEFAULT_BOXCOLOR,b.fillRect(0.5*(f-10),-0.5*(f+10),10,10));b.globalAlpha=r;if(a.onDrawTitleText)a.onDrawTitleText(b,f,d,this.ds.scale,this.title_text_font,c);!l&&(b.font=this.title_text_font,l=a.getTitle())&&(b.fillStyle=c?"white":a.constructor.title_text_color||this.node_title_color,a.flags.collapsed?(b.textAlign="center",r=b.measureText(l),b.fillText(l,f+0.5*r.width,e.NODE_TITLE_TEXT_Y- +f),b.textAlign="left"):(b.textAlign="left",b.fillText(l,f,e.NODE_TITLE_TEXT_Y-f)));if(a.onDrawTitle)a.onDrawTitle(b)}if(c){if(a.onBounding)a.onBounding(A);q==e.TRANSPARENT_TITLE&&(A[1]-=f,A[3]+=f);b.lineWidth=1;b.globalAlpha=0.8;b.beginPath();p==e.BOX_SHAPE?b.rect(-6+A[0],-6+A[1],12+A[2],12+A[3]):p==e.ROUND_SHAPE||p==e.CARD_SHAPE&&a.flags.collapsed?b.roundRect(-6+A[0],-6+A[1],12+A[2],12+A[3],2*this.round_radius):p==e.CARD_SHAPE?b.roundRect(-6+A[0],-6+A[1],12+A[2],12+A[3],2*this.round_radius,2):p== +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=h;b.globalAlpha=1}};var p=new Float32Array(4),s=new Float32Array(4),w=new Float32Array(2),l=new Float32Array(2);g.prototype.drawConnections=function(a){var b=e.getTime(),d=this.visible_area;p[0]=d[0]-20;p[1]=d[1]-20;p[2]=d[2]+40;p[3]=d[3]+40;a.lineWidth=this.connections_width;a.fillStyle="#AAA";a.strokeStyle="#AAA";a.globalAlpha=this.editor_alpha;for(var d=this.graph._nodes,h=0,f=d.length;h< +f;++h){var c=d[h];if(c.inputs&&c.inputs.length)for(var r=0;rs[2]&&(s[0]+=s[2],s[2]=Math.abs(s[2]));0>s[3]&&(s[1]+=s[3],s[3]=Math.abs(s[3]));if(v(s,p)){var n=q.outputs[t],t=c.inputs[r];if(n&& +t&&(q=n.dir||(q.horizontal?e.DOWN:e.RIGHT),t=t.dir||(c.horizontal?e.UP:e.LEFT),this.renderLink(a,k,u,g,!1,0,null,q,t),g&&g._last_time&&1E3>b-g._last_time)){var n=2-0.002*(b-g._last_time),J=a.globalAlpha;a.globalAlpha=J*n;this.renderLink(a,k,u,g,!0,n,"white",q,t);a.globalAlpha=J}}}}}}a.globalAlpha=1};g.prototype.renderLink=function(a,b,d,h,f,c,r,l,p,q){h&&this.visible_links.push(h);!r&&h&&(r=h.color||g.link_type_colors[h.type]);r||(r=this.default_link_color);null!=h&&this.highlighted_links[h.id]&& +(r="#FFF");l=l||e.RIGHT;p=p||e.LEFT;var s=C(b,d);this.render_connections_border&&0.6b[1]?0:Math.PI,a.save(),a.translate(u[0],u[1]),a.rotate(t),a.beginPath(),a.moveTo(-5,-3),a.lineTo(0,7),a.lineTo(5,-3),a.fill(),a.restore(),a.save(),a.translate(q[0],q[1]),a.rotate(k), +a.beginPath(),a.moveTo(-5,-3),a.lineTo(0,7),a.lineTo(5,-3),a.fill(),a.restore()),a.beginPath(),a.arc(f[0],f[1],5,0,2*Math.PI),a.fill());if(c)for(a.fillStyle=r,u=0;5>u;++u)c=(0.001*e.getTime()+0.2*u)%1,f=this.computeConnectionPoint(b,d,c,l,p),a.beginPath(),a.arc(f[0],f[1],5,0,2*Math.PI),a.fill()};g.prototype.computeConnectionPoint=function(a,b,d,h,f){h=h||e.RIGHT;f=f||e.LEFT;var c=C(a,b),r=[a[0],a[1]],l=[b[0],b[1]];switch(h){case e.LEFT:r[0]+=-0.25*c;break;case e.RIGHT:r[0]+=0.25*c;break;case e.UP:r[1]+= +-0.25*c;break;case e.DOWN:r[1]+=0.25*c}switch(f){case e.LEFT:l[0]+=-0.25*c;break;case e.RIGHT:l[0]+=0.25*c;break;case e.UP:l[1]+=-0.25*c;break;case e.DOWN:l[1]+=0.25*c}h=(1-d)*(1-d)*(1-d);f=3*(1-d)*(1-d)*d;c=3*(1-d)*d*d;d*=d*d;return[h*a[0]+f*r[0]+c*l[0]+d*b[0],h*a[1]+f*r[1]+c*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;du.last_y&&ru.options.max&&(u.value=u.options.max);else if("mousedown"==d.type)if((r=u.options.values)&&r.constructor===Function&&(r= +u.options.values(u,a)),c=40>c?-1:c>l-40?1:0,"number"==u.type)u.value+=0.1*c*(u.options.step||1),null!=u.options.min&&u.valueu.options.max&&(u.value=u.options.max);else if(c){var g=r.constructor===Array?r:Object.keys(r),s=g.indexOf(u.value)+c;s>=r.length&&(s=0);0>s&&(s=g.length-1);u.value=r.constructor===Array?r[s]:r[g[s]]}else new e.ContextMenu(r,{scale:Math.max(1,this.ds.scale),event:d,className:"dark",callback:s.bind(u)},g),s= +function(a,b,d){this.value=a;f(this,a);p.dirty_canvas=!0;return!1};else"mouseup"==d.type&&"number"==u.type&&(c=40>c?-1:c>l-40?1:0,200>d.click_time&&0==c&&this.prompt("Value",u.value,function(a){this.value=Number(a);f(this,this.value)}.bind(u),d));h!=u.value&&setTimeout(function(){f(this,this.value)}.bind(u),20);this.dirty_canvas=!0;break;case "toggle":"mousedown"==d.type&&(u.value=!u.value,setTimeout(function(){f(u,u.value)},20));break;case "string":case "text":"mousedown"==d.type&&this.prompt("Value", +u.value,function(a){this.value=a;f(this,a)}.bind(u),d);break;default:u.mouse&&u.mouse(ctx,d,[c,r],a)}return u}}return null};g.prototype.drawGroups=function(a,b){if(this.graph){var d=this.graph._groups;b.save();b.globalAlpha=0.5*this.editor_alpha;for(var h=0;hd&&0.01>b.editor_alpha&&(clearInterval(h),1>d&&(b.live_mode=!0));1"+p+""+a+"",value:p});if(l.length)return new e.ContextMenu(l,{event:d,callback:c,parentMenu:h,allow_html:!0,node:f},b),!1}};g.decodeHTML=function(a){var b=document.createElement("div");b.innerText=a;return b.innerHTML};g.onResizeNode=function(a, +b,d,h,f){f&&(f.size=f.computeSize(),f.setDirtyCanvas(!0,!0))};g.prototype.showLinkMenu=function(a,b){var d=this;console.log(a);var h=new e.ContextMenu(["Add Node",null,"Delete"],{event:b,title:null!=a.data?a.data.constructor.name:null,callback:function(b,c,e){switch(b){case "Add Node":g.onMenuAdd(null,null,e,h,function(b){console.log("node autoconnect");var f=d.graph.getNodeById(a.origin_id),h=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==h.inputs[0].type&&(f.connect(a.origin_slot,b,0),b.connect(0,h,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,h,f){function e(){var b=p.value;"Number"==a.type?b=Number(b):"Boolean"==a.type&&(b=Boolean(b));f[c]=b;l.parentNode&&l.parentNode.removeChild(l);f.setDirtyCanvas(!0,!0)}var c=a.property||"title";b=f[c];var l=document.createElement("div");l.className="graphdialog"; +l.innerHTML="";l.querySelector(".name").innerText=c;var p=l.querySelector("input");p&&(p.value=b,p.addEventListener("blur",function(a){this.focus()}),p.addEventListener("keydown",function(a){13==a.keyCode&&(e(),a.preventDefault(),a.stopPropagation())}));b=g.active_canvas.canvas;d=b.getBoundingClientRect();var q=h=-20;d&&(h-=d.left,q-=d.top);event?(l.style.left=event.clientX+h+"px",l.style.top=event.clientY+q+ +"px"):(l.style.left=0.5*b.width+h+"px",l.style.top=0.5*b.height+q+"px");l.querySelector("button").addEventListener("click",e);b.parentNode.appendChild(l)};g.prototype.prompt=function(a,b,d,h){var f=this;a=a||"";var c=!1,e=document.createElement("div");e.className="graphdialog rounded";e.innerHTML=" ";e.close=function(){f.prompt_box=null;e.parentNode&&e.parentNode.removeChild(e)};1g.search_limit)break}}q=null;if(Array.prototype.filter)q=Object.keys(e.registered_node_types).filter(s);else for(l in q= +[],e.registered_node_types)s(l)&&q.push(l);for(l=0;lg.search_limit);l++);var s=function(a){var b=e.registered_node_types[a];return r&&b.filter!=r?!1:-1!==a.toLowerCase().indexOf(d)}}}var f=this,c=g.active_canvas,l=c.canvas,p=l.ownerDocument||document,q=document.createElement("div");q.className="litegraph litesearchbox graphdialog rounded";q.innerHTML="Search
"; +q.close=function(){f.search_box=null;p.body.focus();p.body.style.overflow="";setTimeout(function(){f.canvas.focus()},20);q.parentNode&&q.parentNode.removeChild(q)};var s=null;1l.height-200&&(k.style.maxHeight=l.height-a.layerY-20+"px");v.focus();return q};g.prototype.showEditPropertyValue=function(a,b,d){function h(){f(u.value)}function f(f){"number"==typeof a.properties[b]&&(f=Number(f));if("array"==c||"object"==c)f=JSON.parse(f);a.properties[b]=f;a._graph&&a._graph._version++;if(a.onPropertyChanged)a.onPropertyChanged(b, +f);if(d.onclose)d.onclose();g.close();a.setDirtyCanvas(!0,!0)}if(a&&void 0!==a.properties[b]){d=d||{};var e=a.getPropertyInfo(b),c=e.type,l="";if("string"==c||"number"==c||"array"==c||"object"==c)l="";else if("enum"==c&&e.values){var l=""}else if("boolean"== +c)l="";else{console.warn("unknown type: "+c);return}var g=this.createDialog(""+b+""+l+"",d);if("enum"==c&&e.values){var u=g.querySelector("select");u.addEventListener("change",function(a){f(a.target.value)})}else if("boolean"==c)(u=g.querySelector("input"))&&u.addEventListener("click",function(a){f(!!u.checked)});else if(u=g.querySelector("input"))u.addEventListener("blur", +function(a){this.focus()}),p=void 0!==a.properties[b]?a.properties[b]:"",p=JSON.stringify(p),u.value=p,u.addEventListener("keydown",function(a){13==a.keyCode&&(h(),a.preventDefault(),a.stopPropagation())});g.querySelector("button").addEventListener("click",h);return g}};g.prototype.createDialog=function(a,b){b=b||{};var d=document.createElement("div");d.className="graphdialog";d.innerHTML=a;var h=this.canvas.getBoundingClientRect(),f=-20,e=-20;h&&(f-=h.left,e-=h.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,h,f){f.collapse()};g.onMenuNodePin=function(a,b,d,h,f){f.pin()};g.onMenuNodeMode=function(a,b,d,h,f){new e.ContextMenu(["Always","On Event","On Trigger","Never"],{event:d,callback:function(a){if(f)switch(a){case "On Event":f.mode= +e.ON_EVENT;break;case "On Trigger":f.mode=e.ON_TRIGGER;break;case "Never":f.mode=e.NEVER;break;default:f.mode=e.ALWAYS}},parentMenu:h,node:f});return!1};g.onMenuNodeColors=function(a,b,d,h,f){if(!f)throw"no node for color";b=[];b.push({value:null,content:"No color"});for(var c in g.node_colors)a=g.node_colors[c],a={value:c,content:""+c+""},b.push(a);new e.ContextMenu(b,{event:d,callback:function(a){f&&((a=a.value?g.node_colors[a.value]:null)?f.constructor===e.LGraphGroup?f.color=a.groupcolor:(f.color=a.color,f.bgcolor=a.bgcolor):(delete f.color,delete f.bgcolor),f.setDirtyCanvas(!0,!0))},parentMenu:h,node:f});return!1};g.onMenuNodeShapes=function(a,b,d,h,f){if(!f)throw"no node passed";new e.ContextMenu(e.VALID_SHAPES,{event:d,callback:function(a){f&&(f.shape=a,f.setDirtyCanvas(!0))},parentMenu:h,node:f}); +return!1};g.onMenuNodeRemove=function(a,b,d,h,f){if(!f)throw"no node passed";!1!==f.removable&&(f.graph.remove(f),f.setDirtyCanvas(!0,!0))};g.onMenuNodeClone=function(a,b,d,h,f){!1!=f.clonable&&(a=f.clone())&&(a.pos=[f.pos[0]+5,f.pos[1]+5],f.graph.add(a),f.setDirtyCanvas(!0,!0))};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(){var a=null;this.getMenuOptions?a=this.getMenuOptions():(a=[{content:"Add Node",has_submenu:!0,callback:g.onMenuAdd},{content:"Add Group",callback:g.onGroupAdd}],this._graph_stack&& +0Name",f),l=c.querySelector("input");l&&e&&(l.value=e.label||"");c.querySelector("button").addEventListener("click",function(a){l.value&&(e&&(e.label=l.value),d.setDirty(!0));c.close()})}},extra:a};a&&(c.title=a.type);var l=null;a&&(l=a.getSlotInPosition(b.canvasX,b.canvasY),g.active_node= +a);if(l){f=[];l&&l.output&&l.output.links&&l.output.links.length&&f.push({content:"Disconnect Links",slot:l});var q=l.input||l.output;f.push(q.locked?"Cannot remove":{content:"Remove Slot",slot:l});f.push(q.nameLocked?"Cannot rename":{content:"Rename Slot",slot:l});c.title=(l.input?l.input.type:l.output.type)||"*";l.input&&l.input.type==e.ACTION&&(c.title="Action");l.output&&l.output.type==e.EVENT&&(c.title="Event")}else a?f=this.getNodeMenuOptions(a):(f=this.getCanvasMenuOptions(),(l=this.graph.getGroupOnPos(b.canvasX, +b.canvasY))&&f.push(null,{content:"Edit Group",has_submenu:!0,submenu:{title:"Group",extra:l,options:this.getGroupMenuOptions(l)}}));f&&new e.ContextMenu(f,c,h)};this.CanvasRenderingContext2D&&(CanvasRenderingContext2D.prototype.roundRect=function(a,b,d,h,f,e){void 0===f&&(f=5);void 0===e&&(e=f);this.moveTo(a+f,b);this.lineTo(a+d-f,b);this.quadraticCurveTo(a+d,b,a+d,b+f);this.lineTo(a+d,b+h-e);this.quadraticCurveTo(a+d,b+h,a+d-e,b+h);this.lineTo(a+e,b+h);this.quadraticCurveTo(a,b+h,a,b+h-e);this.lineTo(a, +b+f);this.quadraticCurveTo(a,b,a+f,b)});e.compareObjects=function(a,b){for(var d in a)if(a[d]!=b[d])return!1;return!0};e.distance=C;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=z;e.growBounding=function(a,b,d){ba[2]&&(a[2]=b);da[3]&&(a[3]=d)};e.isInsideBounding=function(a,b){return a[0]b[1][0]||a[1]>b[1][1]?!1:!0};e.overlapBounding=v;e.hex2num=function(a){"#"==a.charAt(0)&&(a=a.slice(1));a=a.toUpperCase();for(var b=Array(3),d=0,h,f,e=0;6>e;e+=2)h="0123456789ABCDEF".indexOf(a.charAt(e)),f="0123456789ABCDEF".indexOf(a.charAt(e+1)),b[d]=16*h+f,d++;return b};e.num2hex=function(a){for(var b="#",d,h,f=0;3>f;f++)d=a[f]/16,h=a[f]%16,b+="0123456789ABCDEF".charAt(d)+"0123456789ABCDEF".charAt(h);return b};E.prototype.addItem=function(a,b,d){function h(a){var b=this.value;b&&b.has_submenu&& +f.call(this,a)}function f(a){var b=this.value,f=!0;e.current_submenu&&e.current_submenu.close(a);if(d.callback){var h=d.callback.call(this,b,d,a,e,d.node);!0===h&&(f=!1)}if(b&&(b.callback&&!d.ignore_item_callbacks&&!0!==b.disabled&&(h=b.callback.call(this,b,d,a,e,d.extra),!0===h&&(f=!1)),b.submenu)){if(!b.submenu.options)throw"ContextMenu submenu needs options";new e.constructor(b.submenu.options,{callback:b.submenu.callback,event:a,parentMenu:e,ignore_item_callbacks:b.submenu.ignore_item_callbacks, +title:b.submenu.title,extra:b.submenu.extra,autoopen:d.autoopen});f=!1}f&&!e.lock&&e.close()}var e=this;d=d||{};var c=document.createElement("div");c.className="litemenu-entry submenu";var l=!1;if(null===b)c.classList.add("separator");else{c.innerHTML=b&&b.title?b.title:a;if(c.value=b)b.disabled&&(l=!0,c.classList.add("disabled")),(b.submenu||b.has_submenu)&&c.classList.add("has_submenu");"function"==typeof b?(c.dataset.value=a,c.onclick_callback=b):c.dataset.value=b;b.className&&(c.className+=" "+ +b.className)}this.root.appendChild(c);l||c.addEventListener("click",f);d.autoopen&&c.addEventListener("mouseenter",h);return c};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,h){var f=document.createEvent("CustomEvent");f.initCustomEvent(b,!0,!0,d);f.srcElement=h;a.dispatchEvent?a.dispatchEvent(f):a.__events&&a.__events.dispatchEvent(f);return f};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,h=a.clientY,f=b.getBoundingClientRect();return f?h>f.top&&hf.left&&dMath.abs(d))return h[1];d=(a-h[0])/d;return h[1]*(1-d)+f[1]*d}}return 0}};B.prototype.draw=function(a,b,d,h,f,e){if(d=this.points){this.size= +b;var c=b[0]-2*this.margin;b=b[1]-2*this.margin;f=f||"#666";a.save();a.translate(this.margin,this.margin);h&&(a.fillStyle="#111",a.fillRect(0,0,c,b),a.fillStyle="#222",a.fillRect(0.5*c,0,1,b),a.strokeStyle="#333",a.strokeRect(0,0,c,b));a.strokeStyle=f;e&&(a.globalAlpha=0.5);a.beginPath();for(h=0;ha[1])){var h=this.size[0]-2*this.margin,f=this.size[1]-2*this.margin,e=a[0]-this.margin,c=a[1]-this.margin;this.selected=this.getCloserPoint([e,c],30/b.ds.scale);-1==this.selected&&(h=[e/h,1-c/f],d.push(h),d.sort(function(a,b){return a[0]-b[0]}),this.selected=d.indexOf(h),this.must_update=!0);if(-1!=this.selected)return!0}};B.prototype.onMouseMove=function(a,b){var d=this.points;if(d){var h= +this.selected;if(!(0>h)){var f=(a[0]-this.margin)/(this.size[0]-2*this.margin),e=(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);var c=d[h];if(c){var l=0==h||h==d.length-1;!l&&(-10>a[0]||a[0]>this.size[0]+10||-10>a[1]||a[1]>this.size[1]+10)?(d.splice(h,1),this.selected=-1):(c[0]=l?0==h?0:1:Math.clamp(f,0,1),c[1]=1-Math.clamp(e,0,1),d.sort(function(a,b){return a[0]-b[0]}),this.selected=d.indexOf(c),this.must_update= +!0)}}}};B.prototype.onMouseUp=function(a,b){this.selected=-1;return!1};B.prototype.getCloserPoint=function(a,b){var d=this.points;if(!d)return-1;b=b||30;for(var h=this.size[0]-2*this.margin,f=this.size[1]-2*this.margin,e=d.length,c=[0,0],l=1E6,q=-1,p=0;pl||g>b||(q=p,l=g)}return q};e.CurveEditor=B;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:dthis.size[0]-l.NODE_TITLE_HEIGHT&&0>b[1]){var h=this;setTimeout(function(){d.openSubgraph(h.subgraph)},10)}};m.prototype.onAction=function(a,b){this.subgraph.onAction(a,b)};m.prototype.onExecute=function(){if(this.enabled=this.getInputOrProperty("enabled")){if(this.inputs)for(var a=0;a=e?this.trigger(null,g):this._pending.push([e,g])};g.prototype.onExecute=function(){var c=1E3* +this.graph.elapsed_time;this.isInputConnected(1)&&(this.properties.time_in_ms=this.getInputData(1));for(var g=0;ge[1]))return this.old_y=c.canvasY,this.captureInput(!0),this.mouse_captured=!0};n.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/n.pixels_threshold;this._remainder=c%1;c=Math.clamp(this.properties.value+ -(c|0)*this.properties.step,this.properties.min,this.properties.max);this.properties.value=c;this.graph._version++;this.setDirtyCanvas(!0)}};n.prototype.onMouseUp=function(c,e){200>c.click_time&&(this.properties.value=Math.clamp(this.properties.value+(e[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))};B.registerNodeType("widget/number",n);l.title= -"Combo";l.desc="Widget to select from a list";l.prototype.onExecute=function(){this.setOutputData(0,this.properties.value)};l.prototype.onPropertyChanged=function(c,e){"values"==c?(this._values=e.split(";"),this.widget.options.values=this._values):"value"==c&&(this.widget.value=e)};B.registerNodeType("widget/combo",l);x.title="Knob";x.desc="Circular controller";x.size=[80,100];x.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],q=0.5*this.size[1],l=0.5*Math.min(this.size[0],this.size[1])-5;c.globalAlpha=1;c.save();c.translate(e,q);c.rotate(0.75*Math.PI);c.fillStyle="rgba(0,0,0,0.5)";c.beginPath();c.moveTo(0,0);c.arc(0,0,l,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,l-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,q,0.75*l,0,2*Math.PI,!0);c.fill();c.fillStyle=this.mouseOver?"white":this.properties.color;c.beginPath();var s=this.value*Math.PI*1.5+0.75*Math.PI;c.arc(e+Math.cos(s)*l*0.65,q+Math.sin(s)*l*0.65,0.05*l,0,2*Math.PI,!0);c.fill();c.fillStyle=this.mouseOver?"white":"#AAA";c.font=Math.floor(0.5*l)+"px Arial";c.textAlign="center";c.fillText(this.properties.value.toFixed(this.properties.precision),e,q+0.15*l)}};x.prototype.onExecute=function(){this.setOutputData(0, +this.old_y=-1;this._precision=this._remainder=0;this.mouse_captured=!1}function k(){this.addOutput("","string");this.addOutput("change",B.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(q){c.properties.value=q;c.triggerSlot(1,q)},{property:"value",values:this._values})}function x(){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(q){c.properties.value=q},this.properties);this.widgets_up=!0}function C(){this.size=[160,26];this.addOutput("","number");this.properties={color:"#7AF",min:0,max:1,value:0.5};this.value=-1}function z(){this.size= +[160,26];this.addInput("","number");this.properties={min:0,max:1,value:0,color:"#AAF"}}function v(){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 B=y.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 q=this.properties.font_size||30;e.textAlign="center";e.fillStyle=this.clicked?"black":"white";e.font=q+"px "+c.font;e.fillText(this.properties.text,0.5*this.size[0],0.5*this.size[1]+0.3*q);e.textAlign= +"left"}};c.prototype.onMouseDown=function(c,q){if(1q[1]))return this.old_y=c.canvasY,this.captureInput(!0),this.mouse_captured=!0};n.prototype.onMouseMove=function(c){if(this.mouse_captured){var q=this.old_y-c.canvasY;c.shiftKey&&(q*=10);if(c.metaKey||c.altKey)q*=0.1;this.old_y=c.canvasY;c=this._remainder+q/n.pixels_threshold;this._remainder=c%1;c=Math.clamp(this.properties.value+ +(c|0)*this.properties.step,this.properties.min,this.properties.max);this.properties.value=c;this.graph._version++;this.setDirtyCanvas(!0)}};n.prototype.onMouseUp=function(c,q){200>c.click_time&&(this.properties.value=Math.clamp(this.properties.value+(q[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))};B.registerNodeType("widget/number",n);k.title= +"Combo";k.desc="Widget to select from a list";k.prototype.onExecute=function(){this.setOutputData(0,this.properties.value)};k.prototype.onPropertyChanged=function(c,q){"values"==c?(this._values=q.split(";"),this.widget.options.values=this._values):"value"==c&&(this.widget.value=q)};B.registerNodeType("widget/combo",k);x.title="Knob";x.desc="Circular controller";x.size=[80,100];x.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 q=0.5*this.size[0],g=0.5*this.size[1],k=0.5*Math.min(this.size[0],this.size[1])-5;c.globalAlpha=1;c.save();c.translate(q,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,k,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,k-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(q,g,0.75*k,0,2*Math.PI,!0);c.fill();c.fillStyle=this.mouseOver?"white":this.properties.color;c.beginPath();var p=this.value*Math.PI*1.5+0.75*Math.PI;c.arc(q+Math.cos(p)*k*0.65,g+Math.sin(p)*k*0.65,0.05*k,0,2*Math.PI,!0);c.fill();c.fillStyle=this.mouseOver?"white":"#AAA";c.font=Math.floor(0.5*k)+"px Arial";c.textAlign="center";c.fillText(this.properties.value.toFixed(this.properties.precision),q,g+0.15*k)}};x.prototype.onExecute=function(){this.setOutputData(0, this.properties.value);this.boxcolor=B.colorToString([this.value,this.value,this.value])};x.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]||B.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};x.prototype.onMouseMove=function(c){if(this.oldmouse){c=[c.canvasX- -this.pos[0],c.canvasY-this.pos[1]];var e=this.value,e=e-0.01*(c[1]-this.oldmouse[1]);1e&&(e=0);this.value=e;this.properties.value=this.properties.min+(this.properties.max-this.properties.min)*this.value;this.oldmouse=c;this.setDirtyCanvas(!0)}};x.prototype.onMouseUp=function(c){this.oldmouse&&(this.oldmouse=null,this.captureInput(!1))};x.prototype.onPropertyChanged=function(c,e){if("min"==c||"max"==c||"value"==c)return this.properties[c]=parseFloat(e),!0};B.registerNodeType("widget/knob", -x);e.title="Inner Slider";e.prototype.onPropertyChanged=function(c,e){"value"==c&&(this.slider.value=e)};e.prototype.onExecute=function(){this.setOutputData(0,this.properties.value)};B.registerNodeType("widget/internal_slider",e);C.title="H.Slider";C.desc="Linear slider controller";C.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, +this.pos[0],c.canvasY-this.pos[1]];var g=this.value,g=g-0.01*(c[1]-this.oldmouse[1]);1g&&(g=0);this.value=g;this.properties.value=this.properties.min+(this.properties.max-this.properties.min)*this.value;this.oldmouse=c;this.setDirtyCanvas(!0)}};x.prototype.onMouseUp=function(c){this.oldmouse&&(this.oldmouse=null,this.captureInput(!1))};x.prototype.onPropertyChanged=function(c,g){if("min"==c||"max"==c||"value"==c)return this.properties[c]=parseFloat(g),!0};B.registerNodeType("widget/knob", +x);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)};B.registerNodeType("widget/internal_slider",g);C.title="H.Slider";C.desc="Linear slider controller";C.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()};C.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=B.colorToString([this.value,this.value,this.value])};C.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};C.prototype.onMouseMove=function(c){if(this.oldmouse){c=[c.canvasX-this.pos[0],c.canvasY-this.pos[1]];var e=this.value,e=e+(c[0]-this.oldmouse[0])/this.size[0];1e&&(e=0);this.value=e;this.oldmouse=c;this.setDirtyCanvas(!0)}};C.prototype.onMouseUp=function(c){this.oldmouse=null;this.captureInput(!1)};C.prototype.onMouseLeave=function(c){};B.registerNodeType("widget/hslider",C);z.title="Progress";z.desc="Shows data in linear progress";z.prototype.onExecute= -function(){var c=this.getInputData(0);void 0!=c&&(this.properties.value=c)};z.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)};B.registerNodeType("widget/progress",z);v.title="Text";v.desc="Shows the input value";v.widgets=[{name:"resize",text:"Resize box",type:"button"},{name:"led_text", -text:"LED",type:"minibutton"},{name:"normal_text",text:"Normal",type:"minibutton"}];v.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 q=this.properties.fontsize;c.textAlign=this.properties.align;c.font=q.toString()+"px "+this.properties.font;this.str="number"==typeof e?e.toFixed(this.properties.decimals): -e;if("string"==typeof this.str){var e=this.str.split("\\n"),l;for(l in e)c.fillText(e[l],"left"==this.properties.align?15:this.size[0]-15,-0.15*q+q*(parseInt(l)+1))}c.shadowColor="transparent";this.last_ctx=c;c.textAlign="left"};v.prototype.onExecute=function(){var c=this.getInputData(0);null!=c&&(this.properties.value=c)};v.prototype.resize=function(){if(this.last_ctx){var c=this.str.split("\\n");this.last_ctx.font=this.properties.fontsize+"px "+this.properties.font;var e=0,q;for(q in c){var l=this.last_ctx.measureText(c[q]).width; -eg&&(g=0);this.value=g;this.oldmouse=c;this.setDirtyCanvas(!0)}};C.prototype.onMouseUp=function(c){this.oldmouse=null;this.captureInput(!1)};C.prototype.onMouseLeave=function(c){};B.registerNodeType("widget/hslider",C);z.title="Progress";z.desc="Shows data in linear progress";z.prototype.onExecute= +function(){var c=this.getInputData(0);void 0!=c&&(this.properties.value=c)};z.prototype.onDrawForeground=function(c){c.lineWidth=1;c.fillStyle=this.properties.color;var g=(this.properties.value-this.properties.min)/(this.properties.max-this.properties.min),g=Math.min(1,g),g=Math.max(0,g);c.fillRect(2,2,(this.size[0]-4)*g,this.size[1]-4)};B.registerNodeType("widget/progress",z);v.title="Text";v.desc="Shows the input value";v.widgets=[{name:"resize",text:"Resize box",type:"button"},{name:"led_text", +text:"LED",type:"minibutton"},{name:"normal_text",text:"Normal",type:"minibutton"}];v.prototype.onDrawForeground=function(c){c.fillStyle=this.properties.color;var g=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 k=this.properties.fontsize;c.textAlign=this.properties.align;c.font=k.toString()+"px "+this.properties.font;this.str="number"==typeof g?g.toFixed(this.properties.decimals): +g;if("string"==typeof this.str){var g=this.str.split("\\n"),n;for(n in g)c.fillText(g[n],"left"==this.properties.align?15:this.size[0]-15,-0.15*k+k*(parseInt(n)+1))}c.shadowColor="transparent";this.last_ctx=c;c.textAlign="left"};v.prototype.onExecute=function(){var c=this.getInputData(0);null!=c&&(this.properties.value=c)};v.prototype.resize=function(){if(this.last_ctx){var c=this.str.split("\\n");this.last_ctx.font=this.properties.fontsize+"px "+this.properties.font;var g=0,k;for(k in c){var n=this.last_ctx.measureText(c[k]).width; +gl?n.xbox.axes.lx:0,this._left_axis[1]=Math.abs(n.xbox.axes.ly)>l?n.xbox.axes.ly:0,this._right_axis[0]=Math.abs(n.xbox.axes.rx)>l?n.xbox.axes.rx:0,this._right_axis[1]=Math.abs(n.xbox.axes.ry)>l?n.xbox.axes.ry:0,this._triggers[0]=Math.abs(n.xbox.axes.ltrigger)>l?n.xbox.axes.ltrigger: -0,this._triggers[1]=Math.abs(n.xbox.axes.rtrigger)>l?n.xbox.axes.rtrigger:0);if(this.outputs)for(l=0;ln;n++)if(l[n]){n=l[n];l=this.xbox_mapping;l||(l=this.xbox_mapping={axes:[], -buttons:{},hat:"",hatmap:c.CENTER});l.axes.lx=n.axes[0];l.axes.ly=n.axes[1];l.axes.rx=n.axes[2];l.axes.ry=n.axes[3];l.axes.ltrigger=n.buttons[6].value;l.axes.rtrigger=n.buttons[7].value;l.hat="";l.hatmap=c.CENTER;for(var m=0;mm)l.buttons[c.mapping_array[m]]=n.buttons[m].pressed,n.buttons[m].was_pressed&&this.trigger(c.mapping_array[m]+"_button_event");else switch(m){case 12:n.buttons[m].pressed&&(l.hat+="up",l.hatmap|=c.UP); -break;case 13:n.buttons[m].pressed&&(l.hat+="down",l.hatmap|=c.DOWN);break;case 14:n.buttons[m].pressed&&(l.hat+="left",l.hatmap|=c.LEFT);break;case 15:n.buttons[m].pressed&&(l.hat+="right",l.hatmap|=c.RIGHT);break;case 16:l.buttons.home=n.buttons[m].pressed}n.xbox=l;return n}};c.prototype.onDrawBackground=function(c){if(!this.flags.collapsed){var l=this._left_axis,m=this._right_axis;c.strokeStyle="#88A";c.strokeRect(0.5*(l[0]+1)*this.size[0]-4,0.5*(l[1]+1)*this.size[1]-4,8,8);c.strokeStyle="#8A8"; -c.strokeRect(0.5*(m[0]+1)*this.size[0]-4,0.5*(m[1]+1)*this.size[1]-4,8,8);l=this.size[1]/this._current_buttons.length;c.fillStyle="#AEB";for(m=0;mk?n.xbox.axes.lx:0,this._left_axis[1]=Math.abs(n.xbox.axes.ly)>k?n.xbox.axes.ly:0,this._right_axis[0]=Math.abs(n.xbox.axes.rx)>k?n.xbox.axes.rx:0,this._right_axis[1]=Math.abs(n.xbox.axes.ry)>k?n.xbox.axes.ry:0,this._triggers[0]=Math.abs(n.xbox.axes.ltrigger)>k?n.xbox.axes.ltrigger: +0,this._triggers[1]=Math.abs(n.xbox.axes.rtrigger)>k?n.xbox.axes.rtrigger:0);if(this.outputs)for(k=0;kn;n++)if(k[n]){n=k[n];k=this.xbox_mapping;k||(k=this.xbox_mapping={axes:[], +buttons:{},hat:"",hatmap:c.CENTER});k.axes.lx=n.axes[0];k.axes.ly=n.axes[1];k.axes.rx=n.axes[2];k.axes.ry=n.axes[3];k.axes.ltrigger=n.buttons[6].value;k.axes.rtrigger=n.buttons[7].value;k.hat="";k.hatmap=c.CENTER;for(var m=0;mm)k.buttons[c.mapping_array[m]]=n.buttons[m].pressed,n.buttons[m].was_pressed&&this.trigger(c.mapping_array[m]+"_button_event");else switch(m){case 12:n.buttons[m].pressed&&(k.hat+="up",k.hatmap|=c.UP); +break;case 13:n.buttons[m].pressed&&(k.hat+="down",k.hatmap|=c.DOWN);break;case 14:n.buttons[m].pressed&&(k.hat+="left",k.hatmap|=c.LEFT);break;case 15:n.buttons[m].pressed&&(k.hat+="right",k.hatmap|=c.RIGHT);break;case 16:k.buttons.home=n.buttons[m].pressed}n.xbox=k;return n}};c.prototype.onDrawBackground=function(c){if(!this.flags.collapsed){var k=this._left_axis,m=this._right_axis;c.strokeStyle="#88A";c.strokeRect(0.5*(k[0]+1)*this.size[0]-4,0.5*(k[1]+1)*this.size[1]-4,8,8);c.strokeStyle="#8A8"; +c.strokeRect(0.5*(m[0]+1)*this.size[0]-4,0.5*(m[1]+1)*this.size[1]-4,8,8);k=this.size[1]/this._current_buttons.length;c.fillStyle="#AEB";for(m=0;m","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 k(){this.addInput("x","number");this.addInput("y","number");this.addOutput("","number");this.properties={x:1,y:1,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",E.allow_scripts,function(a){E.allow_scripts=a});this._func=null}function f(){this.addInput("vec2","vec2");this.addOutput("x","number");this.addOutput("y","number")}function K(){this.addInputs([["x", -"number"],["y","number"]]);this.addOutput("vec2","vec2");this.properties={x:0,y:0};this._data=new Float32Array(2)}function w(){this.addInput("vec3","vec3");this.addOutput("x","number");this.addOutput("y","number");this.addOutput("z","number")}function H(){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 I(){this.addInput("vec4","vec4");this.addOutput("x","number");this.addOutput("y","number"); -this.addOutput("z","number");this.addOutput("w","number")}function G(){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 E=y.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;ba&&(a+=1024);var c=Math.floor(a);a-=c;d=e.data[c];c=e.data[1023==c?0:c+1];b&&(a=a*a*a*(a*(6*a-15)+10));return d*(1-a)+c*a};e.prototype.onExecute=function(){var a=this.getInputData(0)||0,a=e.getValue(a,this.properties.smooth), -b=this.properties.min;this._last_v=a*(this.properties.max-b)+b;this.setOutputData(0,this._last_v)};e.prototype.onDrawBackground=function(a){this.outputs[0].label=(this._last_v||0).toFixed(3)};E.registerNodeType("math/noise",e);C.title="Spikes";C.desc="spike every random time";C.prototype.onExecute=function(){var a=this.graph.elapsed_time;this._remaining_time-=a;this._blink_time-=a;a=0;0this._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)};E.registerNodeType("math/spikes",C);z.title="Clamp";z.desc="Clamp number between min and max";z.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))};z.prototype.getCode=function(a){a="";this.isInputConnected(0)&& -(a+="clamp({{0}},"+this.properties.min+","+this.properties.max+")");return a};E.registerNodeType("math/clamp",z);v.title="Lerp";v.desc="Linear Interpolation";v.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)};v.prototype.onGetInputs=function(){return[["f","number"]]};E.registerNodeType("math/lerp",v);D.title="Abs";D.desc="Absolute";D.prototype.onExecute= -function(){var a=this.getInputData(0);null!=a&&this.setOutputData(0,Math.abs(a))};E.registerNodeType("math/abs",D);B.title="Floor";B.desc="Floor number to remove fractional part";B.prototype.onExecute=function(){var a=this.getInputData(0);null!=a&&this.setOutputData(0,Math.floor(a))};E.registerNodeType("math/floor",B);g.title="Frac";g.desc="Returns fractional part";g.prototype.onExecute=function(){var a=this.getInputData(0);null!=a&&this.setOutputData(0,a%1)};E.registerNodeType("math/frac",g);r.title= -"Smoothstep";r.desc="Smoothstep";r.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,1);this.setOutputData(0,a*a*(3-2*a))}};E.registerNodeType("math/smoothstep",r);q.title="Scale";q.desc="v * factor";q.prototype.onExecute=function(){var a=this.getInputData(0);null!=a&&this.setOutputData(0,a*this.properties.factor)};E.registerNodeType("math/scale",q);A.title="Gate";A.desc="if v is true, then outputs A, otherwise B"; -A.prototype.onExecute=function(){var a=this.getInputData(0);this.setOutputData(0,this.getInputData(a?1:2))};E.registerNodeType("math/gate",A);s.title="Average";s.desc="Average Filter";s.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;db&&(b=1);this.properties.samples= -Math.round(b);var d=this._values;this._values=new Float32Array(this.properties.samples);d.length<=this._values.length?this._values.set(d):this._values.set(d.subarray(0,this._values.length))};E.registerNodeType("math/average",s);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)};E.registerNodeType("math/tendTo", -p);t.values="+ - * / % ^ max min".split(" ");t.title="Operation";t.desc="Easy math operators";t["@OP"]={type:"enum",title:"operation",values:t.values};t.size=[100,60];t.prototype.getTitle=function(){return"max"==this.properties.OP||"min"==this.properties.OP?this.properties.OP+"(A,B)":"A "+this.properties.OP+" B"};t.prototype.setValue=function(a){"string"==typeof a&&(a=parseFloat(a));this.properties.value=a};t.prototype.onExecute=function(){var a=this.getInputData(0),b=this.getInputData(1);null!=a? -this.properties.A=a:a=this.properties.A;null!=b?this.properties.B=b:b=this.properties.B;var 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)};t.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]+E.NODE_TITLE_HEIGHT)),a.textAlign="left")};E.registerNodeType("math/operation",t);E.registerSearchboxExtra("math/operation","MAX",{properties:{OP:"max"},title:"MAX()"});E.registerSearchboxExtra("math/operation","MIN",{properties:{OP:"min"},title:"MIN()"});h.title="Compare";h.desc="compares between two values";h.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;dB":h=a>b;break;case "A=B":h=a>=b}this.setOutputData(d,h)}}};h.prototype.onGetOutputs=function(){return[["A==B","boolean"],["A!=B","boolean"],["A>B","boolean"],["A=B", -"boolean"],["A<=B","boolean"]]};E.registerNodeType("math/compare",h);E.registerSearchboxExtra("math/compare","==",{outputs:[["A==B","boolean"]],title:"A==B"});E.registerSearchboxExtra("math/compare","!=",{outputs:[["A!=B","boolean"]],title:"A!=B"});E.registerSearchboxExtra("math/compare",">",{outputs:[["A>B","boolean"]],title:"A>B"});E.registerSearchboxExtra("math/compare","<",{outputs:[["A=",{outputs:[["A>=B","boolean"]],title:"A>=B"}); -E.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=":d=a>=b;break;case "||":d=a||b;break;case "&&":d=a&&b}this.setOutputData(0,d);this.setOutputData(1,!d)};E.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)};E.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));for(var d=0,f=this.outputs.length;d< +"nodes/imgs/icon-sin.png"}function h(){this.addInput("x","number");this.addInput("y","number");this.addOutput("","number");this.properties={x:1,y:1,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",D.allow_scripts,function(a){D.allow_scripts=a});this._func=null}function f(){this.addInput("vec2","vec2");this.addOutput("x","number");this.addOutput("y","number")}function K(){this.addInputs([["x", +"number"],["y","number"]]);this.addOutput("vec2","vec2");this.properties={x:0,y:0};this._data=new Float32Array(2)}function r(){this.addInput("vec3","vec3");this.addOutput("x","number");this.addOutput("y","number");this.addOutput("z","number")}function H(){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 I(){this.addInput("vec4","vec4");this.addOutput("x","number");this.addOutput("y","number"); +this.addOutput("z","number");this.addOutput("w","number")}function G(){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 D=y.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;ba&&(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*a-15)+10));return d*(1-a)+c*a};g.prototype.onExecute=function(){var a=this.getInputData(0)||0,a=g.getValue(a,this.properties.smooth), +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)};D.registerNodeType("math/noise",g);C.title="Spikes";C.desc="spike every random time";C.prototype.onExecute=function(){var a=this.graph.elapsed_time;this._remaining_time-=a;this._blink_time-=a;a=0;0this._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)};D.registerNodeType("math/spikes",C);z.title="Clamp";z.desc="Clamp number between min and max";z.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))};z.prototype.getCode=function(a){a="";this.isInputConnected(0)&& +(a+="clamp({{0}},"+this.properties.min+","+this.properties.max+")");return a};D.registerNodeType("math/clamp",z);v.title="Lerp";v.desc="Linear Interpolation";v.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)};v.prototype.onGetInputs=function(){return[["f","number"]]};D.registerNodeType("math/lerp",v);E.title="Abs";E.desc="Absolute";E.prototype.onExecute= +function(){var a=this.getInputData(0);null!=a&&this.setOutputData(0,Math.abs(a))};D.registerNodeType("math/abs",E);B.title="Floor";B.desc="Floor number to remove fractional part";B.prototype.onExecute=function(){var a=this.getInputData(0);null!=a&&this.setOutputData(0,Math.floor(a))};D.registerNodeType("math/floor",B);e.title="Frac";e.desc="Returns fractional part";e.prototype.onExecute=function(){var a=this.getInputData(0);null!=a&&this.setOutputData(0,a%1)};D.registerNodeType("math/frac",e);q.title= +"Smoothstep";q.desc="Smoothstep";q.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,1);this.setOutputData(0,a*a*(3-2*a))}};D.registerNodeType("math/smoothstep",q);t.title="Scale";t.desc="v * factor";t.prototype.onExecute=function(){var a=this.getInputData(0);null!=a&&this.setOutputData(0,a*this.properties.factor)};D.registerNodeType("math/scale",t);A.title="Gate";A.desc="if v is true, then outputs A, otherwise B"; +A.prototype.onExecute=function(){var a=this.getInputData(0);this.setOutputData(0,this.getInputData(a?1:2))};D.registerNodeType("math/gate",A);p.title="Average";p.desc="Average Filter";p.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;db&&(b=1);this.properties.samples= +Math.round(b);var d=this._values;this._values=new Float32Array(this.properties.samples);d.length<=this._values.length?this._values.set(d):this._values.set(d.subarray(0,this._values.length))};D.registerNodeType("math/average",p);s.title="TendTo";s.desc="moves the output value always closer to the input";s.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)};D.registerNodeType("math/tendTo", +s);w.values="+ - * / % ^ max min".split(" ");w.title="Operation";w.desc="Easy math operators";w["@OP"]={type:"enum",title:"operation",values:w.values};w.size=[100,60];w.prototype.getTitle=function(){return"max"==this.properties.OP||"min"==this.properties.OP?this.properties.OP+"(A,B)":"A "+this.properties.OP+" B"};w.prototype.setValue=function(a){"string"==typeof a&&(a=parseFloat(a));this.properties.value=a};w.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)};w.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]+D.NODE_TITLE_HEIGHT)),a.textAlign="left")};D.registerNodeType("math/operation",w);D.registerSearchboxExtra("math/operation","MAX",{properties:{OP:"max"},title:"MAX()"});D.registerSearchboxExtra("math/operation","MIN",{properties:{OP:"min"},title:"MIN()"});l.title="Compare";l.desc="compares between two values";l.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;dB":h=a>b;break;case "A=B":h=a>=b}this.setOutputData(d,h)}}};l.prototype.onGetOutputs=function(){return[["A==B","boolean"],["A!=B","boolean"],["A>B","boolean"],["A=B", +"boolean"],["A<=B","boolean"]]};D.registerNodeType("math/compare",l);D.registerSearchboxExtra("math/compare","==",{outputs:[["A==B","boolean"]],title:"A==B"});D.registerSearchboxExtra("math/compare","!=",{outputs:[["A!=B","boolean"]],title:"A!=B"});D.registerSearchboxExtra("math/compare",">",{outputs:[["A>B","boolean"]],title:"A>B"});D.registerSearchboxExtra("math/compare","<",{outputs:[["A=",{outputs:[["A>=B","boolean"]],title:"A>=B"}); +D.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=":d=a>=b;break;case "||":d=a||b;break;case "&&":d=a&&b}this.setOutputData(0,d);this.setOutputData(1,!d)};D.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)};D.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));for(var d=0,f=this.outputs.length;d< f;++d){var h;switch(this.outputs[d].name){case "sin":h=Math.sin(a);break;case "cos":h=Math.cos(a);break;case "tan":h=Math.tan(a);break;case "asin":h=Math.asin(a);break;case "acos":h=Math.acos(a);break;case "atan":h=Math.atan(a)}this.setOutputData(d,b*h+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"]]}; -E.registerNodeType("math/trigonometry",d);E.registerSearchboxExtra("math/trigonometry","SIN()",{outputs:[["sin","number"]],title:"SIN()"});E.registerSearchboxExtra("math/trigonometry","COS()",{outputs:[["cos","number"]],title:"COS()"});E.registerSearchboxExtra("math/trigonometry","TAN()",{outputs:[["tan","number"]],title:"TAN()"});k.title="Formula";k.desc="Compute formula";k.size=[160,100];s.prototype.onPropertyChanged=function(a,b){"formula"==a&&(this.code_widget.value=b)};k.prototype.onExecute= -function(){if(E.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;var d;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),d=this._func(a,b,this.graph.globaltime),this.boxcolor=null}catch(c){this.boxcolor="red"}this.setOutputData(0,d)}};k.prototype.getTitle=function(){return this._func_code|| -"Formula"};k.prototype.onDrawBackground=function(){var a=this.properties.formula;this.outputs&&this.outputs.length&&(this.outputs[0].label=a)};E.registerNodeType("math/formula",k);f.title="Vec2->XY";f.desc="vector 2 to components";f.prototype.onExecute=function(){var a=this.getInputData(0);null!=a&&(this.setOutputData(0,a[0]),this.setOutputData(1,a[1]))};E.registerNodeType("math3d/vec2-to-xy",f);K.title="XY->Vec2";K.desc="components to vector2";K.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)};E.registerNodeType("math3d/xy-to-vec2",K);w.title="Vec3->XYZ";w.desc="vector 3 to components";w.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]))};E.registerNodeType("math3d/vec3-to-xyz",w);H.title="XYZ->Vec3";H.desc="components to vector3";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.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)};E.registerNodeType("math3d/xyz-to-vec3",H);I.title="Vec4->XYZW";I.desc="vector 4 to components";I.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]))};E.registerNodeType("math3d/vec4-to-xyzw",I);G.title="XYZW->Vec4";G.desc="components to vector4";G.prototype.onExecute=function(){var a=this.getInputData(0);null==a&&(a=this.properties.x);var b=this.getInputData(1);null==b&&(b=this.properties.y);var 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)};E.registerNodeType("math3d/xyzw-to-vec4",G);y.glMatrix&&(y=function(){this.addOutput("quat", -"quat");this.properties={x:0,y:0,z:0,w:1};this._value=quat.create()},y.title="Quaternion",y.desc="quaternion",y.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)},E.registerNodeType("math3d/quaternion",y),y=function(){this.addInputs([["degrees","number"],["axis","vec3"]]);this.addOutput("quat","quat");this.properties={angle:90,axis:vec3.fromValues(0,1, -0)};this._value=quat.create()},y.title="Rotation",y.desc="quaternion rotation",y.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)},E.registerNodeType("math3d/rotation",y),y=function(){this.addInputs([["vec3","vec3"],["quat","quat"]]);this.addOutput("result","vec3");this.properties={vec:[0,0,1]}},y.title="Rot. Vec3",y.desc= -"rotate a point",y.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))},E.registerNodeType("math3d/rotate_vec3",y),y=function(){this.addInputs([["A","quat"],["B","quat"]]);this.addOutput("A*B","quat");this._value=quat.create()},y.title="Mult. Quat",y.desc="rotate quaternion",y.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))}},E.registerNodeType("math3d/mult-quat",y),y=function(){this.addInputs([["A","quat"],["B","quat"],["factor","number"]]);this.addOutput("slerp","quat");this.addProperty("factor",0.5);this._value=quat.create()},y.title="Quat Slerp",y.desc="quaternion spherical interpolation",y.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)}}},E.registerNodeType("math3d/quat-slerp",y))})(this); +D.registerNodeType("math/trigonometry",d);D.registerSearchboxExtra("math/trigonometry","SIN()",{outputs:[["sin","number"]],title:"SIN()"});D.registerSearchboxExtra("math/trigonometry","COS()",{outputs:[["cos","number"]],title:"COS()"});D.registerSearchboxExtra("math/trigonometry","TAN()",{outputs:[["tan","number"]],title:"TAN()"});h.title="Formula";h.desc="Compute formula";h.size=[160,100];p.prototype.onPropertyChanged=function(a,b){"formula"==a&&(this.code_widget.value=b)};h.prototype.onExecute= +function(){if(D.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;var d;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),d=this._func(a,b,this.graph.globaltime),this.boxcolor=null}catch(c){this.boxcolor="red"}this.setOutputData(0,d)}};h.prototype.getTitle=function(){return this._func_code|| +"Formula"};h.prototype.onDrawBackground=function(){var a=this.properties.formula;this.outputs&&this.outputs.length&&(this.outputs[0].label=a)};D.registerNodeType("math/formula",h);f.title="Vec2->XY";f.desc="vector 2 to components";f.prototype.onExecute=function(){var a=this.getInputData(0);null!=a&&(this.setOutputData(0,a[0]),this.setOutputData(1,a[1]))};D.registerNodeType("math3d/vec2-to-xy",f);K.title="XY->Vec2";K.desc="components to vector2";K.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)};D.registerNodeType("math3d/xy-to-vec2",K);r.title="Vec3->XYZ";r.desc="vector 3 to components";r.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]))};D.registerNodeType("math3d/vec3-to-xyz",r);H.title="XYZ->Vec3";H.desc="components to vector3";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.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)};D.registerNodeType("math3d/xyz-to-vec3",H);I.title="Vec4->XYZW";I.desc="vector 4 to components";I.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]))};D.registerNodeType("math3d/vec4-to-xyzw",I);G.title="XYZW->Vec4";G.desc="components to vector4";G.prototype.onExecute=function(){var a=this.getInputData(0);null==a&&(a=this.properties.x);var b=this.getInputData(1);null==b&&(b=this.properties.y);var 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)};D.registerNodeType("math3d/xyzw-to-vec4",G);y.glMatrix&&(y=function(){this.addOutput("quat", +"quat");this.properties={x:0,y:0,z:0,w:1};this._value=quat.create()},y.title="Quaternion",y.desc="quaternion",y.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)},D.registerNodeType("math3d/quaternion",y),y=function(){this.addInputs([["degrees","number"],["axis","vec3"]]);this.addOutput("quat","quat");this.properties={angle:90,axis:vec3.fromValues(0,1, +0)};this._value=quat.create()},y.title="Rotation",y.desc="quaternion rotation",y.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)},D.registerNodeType("math3d/rotation",y),y=function(){this.addInputs([["vec3","vec3"],["quat","quat"]]);this.addOutput("result","vec3");this.properties={vec:[0,0,1]}},y.title="Rot. Vec3",y.desc= +"rotate a point",y.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))},D.registerNodeType("math3d/rotate_vec3",y),y=function(){this.addInputs([["A","quat"],["B","quat"]]);this.addOutput("A*B","quat");this._value=quat.create()},y.title="Mult. Quat",y.desc="rotate quaternion",y.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))}},D.registerNodeType("math3d/mult-quat",y),y=function(){this.addInputs([["A","quat"],["B","quat"],["factor","number"]]);this.addOutput("slerp","quat");this.addProperty("factor",0.5);this._value=quat.create()},y.title="Quat Slerp",y.desc="quaternion spherical interpolation",y.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)}}},D.registerNodeType("math3d/quat-slerp",y))})(this); (function(y){function c(){this.addInput("T","vec3");this.addInput("R","vec3");this.addInput("S","vec3");this.addOutput("mat4","mat4");this.properties={T:[0,0,0],R:[0,0,0],S:[1,1,1],R_in_degrees:!0};this._result=mat4.create();this._must_update=!0}function m(){this.addInput("A","number,vec3");this.addInput("B","number,vec3");this.addOutput("=","vec3");this.addProperty("OP","+","enum",{values:m.values});this._result=vec3.create()}function n(){this.addInput("in","vec3");this.addInput("f","number");this.addOutput("out", -"vec3");this.properties={f:1};this._data=new Float32Array(3)}function l(){this.addInput("in","vec3");this.addOutput("out","number")}function x(){this.addInput("in","vec3");this.addOutput("out","vec3");this._data=new Float32Array(3)}function e(){this.addInput("A","vec3");this.addInput("B","vec3");this.addInput("f","vec3");this.addOutput("out","vec3");this.properties={f:0.5};this._data=new Float32Array(3)}function C(){this.addInput("A","vec3");this.addInput("B","vec3");this.addOutput("out","number")} -var z=y.LiteGraph;c.title="mat4";c.temp_quat=new Float32Array([0,0,0,1]);c.temp_mat4=new Float32Array(16);c.temp_vec3=new Float32Array(3);c.prototype.onPropertyChanged=function(c,e){this._must_update=!0};c.prototype.onExecute=function(){var e=this._result,l=c.temp_quat,n=c.temp_mat4,g=c.temp_vec3,r=this.getInputData(0),q=this.getInputData(1),m=this.getInputData(2);if(this._must_update||r||q||m)r=r||this.properties.T,q=q||this.properties.R,m=m||this.properties.S,mat4.identity(e),mat4.translate(e,e, -r),this.properties.R_in_degrees?(g.set(q),vec3.scale(g,g,DEG2RAD),quat.fromEuler(l,g)):quat.fromEuler(l,q),mat4.fromQuat(n,l),mat4.multiply(e,e,n),mat4.scale(e,e,m);this.setOutputData(0,e)};z.registerNodeType("math3d/mat4",c);m.values="+ - * / % ^ max min".split(" ");m.title="Operation";m.desc="Easy math 3D operators";m["@OP"]={type:"enum",title:"operation",values:m.values};m.size=[100,60];m.prototype.getTitle=function(){return"max"==this.properties.OP||"min"==this.properties.OP?this.properties.OP+ -"(A,B)":"A "+this.properties.OP+" B"};m.prototype.onExecute=function(){var c=this.getInputData(0),e=this.getInputData(1);if(null!=c&&null!=e){c.constructor===Number&&(c=[c,c,c]);e.constructor===Number&&(e=[e,e,e]);var l=this._result;switch(this.properties.OP){case "+":l=vec3.add(l,c,e);break;case "-":l=vec3.sub(l,c,e);break;case "x":case "X":case "*":l=vec3.mul(l,c,e);break;case "/":l=vec3.div(l,c,e);break;case "%":l[0]=c[0]%e[0];l[1]=c[1]%e[1];l[2]=c[2]%e[2];break;case "^":l[0]=Math.pow(c[0],e[0]); -l[1]=Math.pow(c[1],e[1]);l[2]=Math.pow(c[2],e[2]);break;case "max":l[0]=Math.max(c[0],e[0]);l[1]=Math.max(c[1],e[1]);l[2]=Math.max(c[2],e[2]);break;case "min":l[0]=Math.min(c[0],e[0]);l[1]=Math.min(c[1],e[1]);l[2]=Math.min(c[2],e[2]);break;default:console.warn("Unknown operation: "+this.properties.OP)}this.setOutputData(0,l)}};m.prototype.onDrawBackground=function(c){this.flags.collapsed||(c.font="40px Arial",c.fillStyle="#666",c.textAlign="center",c.fillText(this.properties.OP,0.5*this.size[0],0.5* -(this.size[1]+z.NODE_TITLE_HEIGHT)),c.textAlign="left")};z.registerNodeType("math3d/operation",m);n.title="vec3_scale";n.desc="scales the components of a vec3";n.prototype.onExecute=function(){var c=this.getInputData(0);if(null!=c){var e=this.getInputData(1);null==e&&(e=this.properties.f);var l=this._data;l[0]=c[0]*e;l[1]=c[1]*e;l[2]=c[2]*e;this.setOutputData(0,l)}};z.registerNodeType("math3d/vec3-scale",n);l.title="vec3_length";l.desc="returns the module of a vector";l.prototype.onExecute=function(){var c= -this.getInputData(0);null!=c&&(c=Math.sqrt(c[0]*c[0]+c[1]*c[1]+c[2]*c[2]),this.setOutputData(0,c))};z.registerNodeType("math3d/vec3-length",l);x.title="vec3_normalize";x.desc="returns the vector normalized";x.prototype.onExecute=function(){var c=this.getInputData(0);if(null!=c){var e=Math.sqrt(c[0]*c[0]+c[1]*c[1]+c[2]*c[2]),l=this._data;l[0]=c[0]/e;l[1]=c[1]/e;l[2]=c[2]/e;this.setOutputData(0,l)}};z.registerNodeType("math3d/vec3-normalize",x);e.title="vec3_lerp";e.desc="returns the interpolated vector"; -e.prototype.onExecute=function(){var c=this.getInputData(0);if(null!=c){var e=this.getInputData(1);if(null!=e){var l=this.getInputOrProperty("f"),g=this._data;g[0]=c[0]*(1-l)+e[0]*l;g[1]=c[1]*(1-l)+e[1]*l;g[2]=c[2]*(1-l)+e[2]*l;this.setOutputData(0,g)}}};z.registerNodeType("math3d/vec3-lerp",e);C.title="vec3_dot";C.desc="returns the dot product";C.prototype.onExecute=function(){var c=this.getInputData(0);if(null!=c){var e=this.getInputData(1);null!=e&&this.setOutputData(0,c[0]*e[0]+c[1]*e[1]+c[2]* -e[2])}};z.registerNodeType("math3d/vec3-dot",C);y.glMatrix?(y=function(){this.addOutput("quat","quat");this.properties={x:0,y:0,z:0,w:1,normalize:!1};this._value=quat.create()},y.title="Quaternion",y.desc="quaternion",y.prototype.onExecute=function(){this._value[0]=this.getInputOrProperty("x");this._value[1]=this.getInputOrProperty("y");this._value[2]=this.getInputOrProperty("z");this._value[3]=this.getInputOrProperty("w");this.properties.normalize&&quat.normalize(this._value,this._value);this.setOutputData(0, -this._value)},y.prototype.onGetInputs=function(){return[["x","number"],["y","number"],["z","number"],["w","number"]]},z.registerNodeType("math3d/quaternion",y),y=function(){this.addInputs([["degrees","number"],["axis","vec3"]]);this.addOutput("quat","quat");this.properties={angle:90,axis:vec3.fromValues(0,1,0)};this._value=quat.create()},y.title="Rotation",y.desc="quaternion rotation",y.prototype.onExecute=function(){var c=this.getInputData(0);null==c&&(c=this.properties.angle);var e=this.getInputData(1); -null==e&&(e=this.properties.axis);c=quat.setAxisAngle(this._value,e,0.0174532925*c);this.setOutputData(0,c)},z.registerNodeType("math3d/rotation",y),y=function(){this.addInputs([["vec3","vec3"],["quat","quat"]]);this.addOutput("result","vec3");this.properties={vec:[0,0,1]}},y.title="Rot. Vec3",y.desc="rotate a point",y.prototype.onExecute=function(){var c=this.getInputData(0);null==c&&(c=this.properties.vec);var e=this.getInputData(1);null==e?this.setOutputData(c):this.setOutputData(0,vec3.transformQuat(vec3.create(), -c,e))},z.registerNodeType("math3d/rotate_vec3",y),y=function(){this.addInputs([["A","quat"],["B","quat"]]);this.addOutput("A*B","quat");this._value=quat.create()},y.title="Mult. Quat",y.desc="rotate quaternion",y.prototype.onExecute=function(){var c=this.getInputData(0);if(null!=c){var e=this.getInputData(1);null!=e&&(c=quat.multiply(this._value,c,e),this.setOutputData(0,c))}},z.registerNodeType("math3d/mult-quat",y),y=function(){this.addInputs([["A","quat"],["B","quat"],["factor","number"]]);this.addOutput("slerp", -"quat");this.addProperty("factor",0.5);this._value=quat.create()},y.title="Quat Slerp",y.desc="quaternion spherical interpolation",y.prototype.onExecute=function(){var c=this.getInputData(0);if(null!=c){var e=this.getInputData(1);if(null!=e){var l=this.properties.factor;null!=this.getInputData(2)&&(l=this.getInputData(2));c=quat.slerp(this._value,c,e,l);this.setOutputData(0,c)}}},z.registerNodeType("math3d/quat-slerp",y),y=function(){this.addInput("vec3","vec3");this.addOutput("remap","vec3");this.addOutput("clamped", -"vec3");this.properties={clamp:!0,range_min:[-1,-1,0],range_max:[1,1,0],target_min:[-1,-1,0],target_max:[1,1,0]};this._value=vec3.create();this._clamped=vec3.create()},y.title="Remap Range",y.desc="remap a 3D range",y.prototype.onExecute=function(){var c=this.getInputData(0);c&&this._value.set(c);for(var c=this.properties.range_min,e=this.properties.range_max,l=this.properties.target_min,g=this.properties.target_max,r=0;3>r;++r){var q=e[r]-c[r];this._clamped[r]=Math.clamp(this._value[r],c[r],e[r]); -0==q?this._value[r]=0.5*(l[r]+g[r]):(q=(this._value[r]-c[r])/q,this.properties.clamp&&(q=Math.clamp(q,0,1)),this._value[r]=l[r]+q*(g[r]-l[r]))}this.setOutputData(0,this._value);this.setOutputData(1,this._clamped)},z.registerNodeType("math3d/remap_range",y)):console.warn("No glmatrix found, some Math3D nodes may not work")})(this); -(function(y){function c(c,l){return c==l}function m(c){return null!=c&&c.constructor===String?c.toUpperCase():c}y=y.LiteGraph;y.wrapFunctionAsNode("string/toString",c,["*"],"String");y.wrapFunctionAsNode("string/compare",c,["String","String"],"Boolean");y.wrapFunctionAsNode("string/concatenate",function(c,l){return void 0===c?l:void 0===l?c:c+l},["String","String"],"String");y.wrapFunctionAsNode("string/contains",function(c,l){return void 0===c||void 0===l?!1:-1!=c.indexOf(l)},["String","String"], +"vec3");this.properties={f:1};this._data=new Float32Array(3)}function k(){this.addInput("in","vec3");this.addOutput("out","number")}function x(){this.addInput("in","vec3");this.addOutput("out","vec3");this._data=new Float32Array(3)}function g(){this.addInput("A","vec3");this.addInput("B","vec3");this.addInput("f","vec3");this.addOutput("out","vec3");this.properties={f:0.5};this._data=new Float32Array(3)}function C(){this.addInput("A","vec3");this.addInput("B","vec3");this.addOutput("out","number")} +var z=y.LiteGraph;c.title="mat4";c.temp_quat=new Float32Array([0,0,0,1]);c.temp_mat4=new Float32Array(16);c.temp_vec3=new Float32Array(3);c.prototype.onPropertyChanged=function(c,g){this._must_update=!0};c.prototype.onExecute=function(){var g=this._result,k=c.temp_quat,n=c.temp_mat4,e=c.temp_vec3,q=this.getInputData(0),t=this.getInputData(1),m=this.getInputData(2);if(this._must_update||q||t||m)q=q||this.properties.T,t=t||this.properties.R,m=m||this.properties.S,mat4.identity(g),mat4.translate(g,g, +q),this.properties.R_in_degrees?(e.set(t),vec3.scale(e,e,DEG2RAD),quat.fromEuler(k,e)):quat.fromEuler(k,t),mat4.fromQuat(n,k),mat4.multiply(g,g,n),mat4.scale(g,g,m);this.setOutputData(0,g)};z.registerNodeType("math3d/mat4",c);m.values="+ - * / % ^ max min".split(" ");m.title="Operation";m.desc="Easy math 3D operators";m["@OP"]={type:"enum",title:"operation",values:m.values};m.size=[100,60];m.prototype.getTitle=function(){return"max"==this.properties.OP||"min"==this.properties.OP?this.properties.OP+ +"(A,B)":"A "+this.properties.OP+" B"};m.prototype.onExecute=function(){var c=this.getInputData(0),g=this.getInputData(1);if(null!=c&&null!=g){c.constructor===Number&&(c=[c,c,c]);g.constructor===Number&&(g=[g,g,g]);var k=this._result;switch(this.properties.OP){case "+":k=vec3.add(k,c,g);break;case "-":k=vec3.sub(k,c,g);break;case "x":case "X":case "*":k=vec3.mul(k,c,g);break;case "/":k=vec3.div(k,c,g);break;case "%":k[0]=c[0]%g[0];k[1]=c[1]%g[1];k[2]=c[2]%g[2];break;case "^":k[0]=Math.pow(c[0],g[0]); +k[1]=Math.pow(c[1],g[1]);k[2]=Math.pow(c[2],g[2]);break;case "max":k[0]=Math.max(c[0],g[0]);k[1]=Math.max(c[1],g[1]);k[2]=Math.max(c[2],g[2]);break;case "min":k[0]=Math.min(c[0],g[0]);k[1]=Math.min(c[1],g[1]);k[2]=Math.min(c[2],g[2]);break;default:console.warn("Unknown operation: "+this.properties.OP)}this.setOutputData(0,k)}};m.prototype.onDrawBackground=function(c){this.flags.collapsed||(c.font="40px Arial",c.fillStyle="#666",c.textAlign="center",c.fillText(this.properties.OP,0.5*this.size[0],0.5* +(this.size[1]+z.NODE_TITLE_HEIGHT)),c.textAlign="left")};z.registerNodeType("math3d/operation",m);n.title="vec3_scale";n.desc="scales the components of a vec3";n.prototype.onExecute=function(){var c=this.getInputData(0);if(null!=c){var g=this.getInputData(1);null==g&&(g=this.properties.f);var k=this._data;k[0]=c[0]*g;k[1]=c[1]*g;k[2]=c[2]*g;this.setOutputData(0,k)}};z.registerNodeType("math3d/vec3-scale",n);k.title="vec3_length";k.desc="returns the module of a vector";k.prototype.onExecute=function(){var c= +this.getInputData(0);null!=c&&(c=Math.sqrt(c[0]*c[0]+c[1]*c[1]+c[2]*c[2]),this.setOutputData(0,c))};z.registerNodeType("math3d/vec3-length",k);x.title="vec3_normalize";x.desc="returns the vector normalized";x.prototype.onExecute=function(){var c=this.getInputData(0);if(null!=c){var g=Math.sqrt(c[0]*c[0]+c[1]*c[1]+c[2]*c[2]),k=this._data;k[0]=c[0]/g;k[1]=c[1]/g;k[2]=c[2]/g;this.setOutputData(0,k)}};z.registerNodeType("math3d/vec3-normalize",x);g.title="vec3_lerp";g.desc="returns the interpolated vector"; +g.prototype.onExecute=function(){var c=this.getInputData(0);if(null!=c){var g=this.getInputData(1);if(null!=g){var k=this.getInputOrProperty("f"),e=this._data;e[0]=c[0]*(1-k)+g[0]*k;e[1]=c[1]*(1-k)+g[1]*k;e[2]=c[2]*(1-k)+g[2]*k;this.setOutputData(0,e)}}};z.registerNodeType("math3d/vec3-lerp",g);C.title="vec3_dot";C.desc="returns the dot product";C.prototype.onExecute=function(){var c=this.getInputData(0);if(null!=c){var g=this.getInputData(1);null!=g&&this.setOutputData(0,c[0]*g[0]+c[1]*g[1]+c[2]* +g[2])}};z.registerNodeType("math3d/vec3-dot",C);y.glMatrix?(y=function(){this.addOutput("quat","quat");this.properties={x:0,y:0,z:0,w:1,normalize:!1};this._value=quat.create()},y.title="Quaternion",y.desc="quaternion",y.prototype.onExecute=function(){this._value[0]=this.getInputOrProperty("x");this._value[1]=this.getInputOrProperty("y");this._value[2]=this.getInputOrProperty("z");this._value[3]=this.getInputOrProperty("w");this.properties.normalize&&quat.normalize(this._value,this._value);this.setOutputData(0, +this._value)},y.prototype.onGetInputs=function(){return[["x","number"],["y","number"],["z","number"],["w","number"]]},z.registerNodeType("math3d/quaternion",y),y=function(){this.addInputs([["degrees","number"],["axis","vec3"]]);this.addOutput("quat","quat");this.properties={angle:90,axis:vec3.fromValues(0,1,0)};this._value=quat.create()},y.title="Rotation",y.desc="quaternion rotation",y.prototype.onExecute=function(){var c=this.getInputData(0);null==c&&(c=this.properties.angle);var g=this.getInputData(1); +null==g&&(g=this.properties.axis);c=quat.setAxisAngle(this._value,g,0.0174532925*c);this.setOutputData(0,c)},z.registerNodeType("math3d/rotation",y),y=function(){this.addInputs([["vec3","vec3"],["quat","quat"]]);this.addOutput("result","vec3");this.properties={vec:[0,0,1]}},y.title="Rot. Vec3",y.desc="rotate a point",y.prototype.onExecute=function(){var c=this.getInputData(0);null==c&&(c=this.properties.vec);var g=this.getInputData(1);null==g?this.setOutputData(c):this.setOutputData(0,vec3.transformQuat(vec3.create(), +c,g))},z.registerNodeType("math3d/rotate_vec3",y),y=function(){this.addInputs([["A","quat"],["B","quat"]]);this.addOutput("A*B","quat");this._value=quat.create()},y.title="Mult. Quat",y.desc="rotate quaternion",y.prototype.onExecute=function(){var c=this.getInputData(0);if(null!=c){var g=this.getInputData(1);null!=g&&(c=quat.multiply(this._value,c,g),this.setOutputData(0,c))}},z.registerNodeType("math3d/mult-quat",y),y=function(){this.addInputs([["A","quat"],["B","quat"],["factor","number"]]);this.addOutput("slerp", +"quat");this.addProperty("factor",0.5);this._value=quat.create()},y.title="Quat Slerp",y.desc="quaternion spherical interpolation",y.prototype.onExecute=function(){var c=this.getInputData(0);if(null!=c){var g=this.getInputData(1);if(null!=g){var k=this.properties.factor;null!=this.getInputData(2)&&(k=this.getInputData(2));c=quat.slerp(this._value,c,g,k);this.setOutputData(0,c)}}},z.registerNodeType("math3d/quat-slerp",y),y=function(){this.addInput("vec3","vec3");this.addOutput("remap","vec3");this.addOutput("clamped", +"vec3");this.properties={clamp:!0,range_min:[-1,-1,0],range_max:[1,1,0],target_min:[-1,-1,0],target_max:[1,1,0]};this._value=vec3.create();this._clamped=vec3.create()},y.title="Remap Range",y.desc="remap a 3D range",y.prototype.onExecute=function(){var c=this.getInputData(0);c&&this._value.set(c);for(var c=this.properties.range_min,g=this.properties.range_max,k=this.properties.target_min,e=this.properties.target_max,q=0;3>q;++q){var n=g[q]-c[q];this._clamped[q]=Math.clamp(this._value[q],c[q],g[q]); +0==n?this._value[q]=0.5*(k[q]+e[q]):(n=(this._value[q]-c[q])/n,this.properties.clamp&&(n=Math.clamp(n,0,1)),this._value[q]=k[q]+n*(e[q]-k[q]))}this.setOutputData(0,this._value);this.setOutputData(1,this._clamped)},z.registerNodeType("math3d/remap_range",y)):console.warn("No glmatrix found, some Math3D nodes may not work")})(this); +(function(y){function c(c,k){return c==k}function m(c){return null!=c&&c.constructor===String?c.toUpperCase():c}y=y.LiteGraph;y.wrapFunctionAsNode("string/toString",c,["*"],"String");y.wrapFunctionAsNode("string/compare",c,["String","String"],"Boolean");y.wrapFunctionAsNode("string/concatenate",function(c,k){return void 0===c?k:void 0===k?c:c+k},["String","String"],"String");y.wrapFunctionAsNode("string/contains",function(c,k){return void 0===c||void 0===k?!1:-1!=c.indexOf(k)},["String","String"], "Boolean");y.wrapFunctionAsNode("string/toUpperCase",m,["String"],"String");y.wrapFunctionAsNode("string/split",m,["String","String"],"Array");y.wrapFunctionAsNode("string/toFixed",function(c){return null!=c&&c.constructor===Number?c.toFixed(this.properties.precision):c},["Number"],"String",{precision:0})})(this); (function(y){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 m(){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 n=y.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)*n.NODE_SLOT_HEIGHT+6;c.beginPath();c.moveTo(50,m);c.lineTo(50,m+n.NODE_SLOT_HEIGHT);c.lineTo(34,m+0.5*n.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]]};n.registerNodeType("logic/selector",c);m.title="Sequence"; m.desc="select one element from a sequence from a string";m.prototype.onPropertyChanged=function(c,n){"sequence"==c&&(this.values=n.split(","))};m.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])};n.registerNodeType("logic/sequence",m)})(this); -(function(y){function c(){this.addInput("A","Number");this.addInput("B","Number");this.addInput("C","Number");this.addInput("D","Number");this.values=[[],[],[],[]];this.properties={scale:2}}function m(){this.addOutput("frame","image");this.properties={url:""}}function n(){this.addInput("f","number");this.addOutput("Color","color");this.properties={colorA:"#444444",colorB:"#44AAFF",colorC:"#44FFAA",colorD:"#FFFFFF"}}function l(){this.addInput("","image,canvas");this.size=[200,200]}function x(){this.addInputs([["img1", -"image"],["img2","image"],["fade","number"]]);this.addOutput("","image");this.properties={fade:0.5,width:512,height:512}}function e(){this.addInput("","image");this.addOutput("","image");this.properties={width:256,height:256,x:0,y:0,scale:1};this.size=[50,20]}function C(){this.addInput("clear",g.ACTION);this.addOutput("","canvas");this.properties={width:512,height:512,autoclear:!0};this.canvas=document.createElement("canvas");this.ctx=this.canvas.getContext("2d")}function z(){this.addInput("canvas", -"canvas");this.addInput("img","image,canvas");this.addInput("x","number");this.addInput("y","number");this.properties={x:0,y:0,opacity:1}}function v(){this.addInput("canvas","canvas");this.addInput("x","number");this.addInput("y","number");this.addInput("w","number");this.addInput("h","number");this.properties={x:0,y:0,w:10,h:10,color:"white",opacity:1}}function D(){this.addInput("t","number");this.addOutputs([["frame","image"],["t","number"],["d","number"]]);this.properties={url:"",use_proxy:!0}} -function B(){this.addOutput("Webcam","image");this.properties={facingMode:"user"};this.boxcolor="black";this.frame=0}var g=y.LiteGraph;c.title="Plot";c.desc="Plots data over time";c.colors=["#FFF","#F99","#9F9","#99F"];c.prototype.onExecute=function(c){if(!this.flags.collapsed){c=this.size;for(var e=0;4>e;++e){var g=this.getInputData(e);if(null!=g){var s=this.values[e];s.push(g);s.length>c[0]&&s.shift()}}}};c.prototype.onDrawBackground=function(e){if(!this.flags.collapsed){var g=this.size,l=0.5*g[1]/ -this.properties.scale,s=c.colors,p=0.5*g[1];e.fillStyle="#000";e.fillRect(0,0,g[0],g[1]);e.strokeStyle="#555";e.beginPath();e.moveTo(0,p);e.lineTo(g[0],p);e.stroke();if(this.inputs)for(var n=0;4>n;++n){var h=this.values[n];if(this.inputs[n]&&this.inputs[n].link){e.strokeStyle=s[n];e.beginPath();var a=h[0]*l*-1+p;e.moveTo(0,Math.clamp(a,0,g[1]));for(var b=1;be;++e){var g=this.getInputData(e);if(null!=g){var p=this.values[e];p.push(g);p.length>c[0]&&p.shift()}}}};c.prototype.onDrawBackground=function(e){if(!this.flags.collapsed){var g=this.size,k=0.5*g[1]/ +this.properties.scale,p=c.colors,s=0.5*g[1];e.fillStyle="#000";e.fillRect(0,0,g[0],g[1]);e.strokeStyle="#555";e.beginPath();e.moveTo(0,s);e.lineTo(g[0],s);e.stroke();if(this.inputs)for(var n=0;4>n;++n){var l=this.values[n];if(this.inputs[n]&&this.inputs[n].link){e.strokeStyle=p[n];e.beginPath();var a=l[0]*k*-1+s;e.moveTo(0,Math.clamp(a,0,g[1]));for(var b=1;be&&(e=0);if(0!=c.length){var g=[0,0,0];if(0==e)g=c[0];else if(1==e)g=c[c.length-1];else{var s=(c.length-1)* -e,e=c[Math.floor(s)],c=c[Math.floor(s)+1],s=s-Math.floor(s);g[0]=e[0]*(1-s)+c[0]*s;g[1]=e[1]*(1-s)+c[1]*s;g[2]=e[2]*(1-s)+c[2]*s}for(var p in g)g[p]/=255;this.boxcolor=colorToString(g);this.setOutputData(0,g)}};g.registerNodeType("color/palette",n);l.title="Frame";l.desc="Frame viewerew";l.widgets=[{name:"resize",text:"Resize box",type:"button"},{name:"view",text:"View Image",type:"button"}];l.prototype.onDrawBackground=function(c){this.frame&&!this.flags.collapsed&&c.drawImage(this.frame,0,0,this.size[0], -this.size[1])};l.prototype.onExecute=function(){this.frame=this.getInputData(0);this.setDirtyCanvas(!0)};l.prototype.onWidget=function(c,e){if("resize"==e.name&&this.frame){var g=this.frame.width,s=this.frame.height;g||null==this.frame.videoWidth||(g=this.frame.videoWidth,s=this.frame.videoHeight);g&&s&&(this.size=[g,s]);this.setDirtyCanvas(!0,!0)}else"view"==e.name&&this.show()};l.prototype.show=function(){showElement&&this.frame&&showElement(this.frame)};g.registerNodeType("graphics/frame",l);x.title= +this.img):this.setOutputData(0,null);this.img&&this.img.dirty&&(this.img.dirty=!1)};m.prototype.onPropertyChanged=function(c,e){this.properties[c]=e;"url"==c&&""!=e&&this.loadImage(e);return!0};m.prototype.loadImage=function(c,g){if(""==c)this.img=null;else{this.img=document.createElement("img");"http"==c.substr(0,4)&&e.proxy&&(c=e.proxy+c.substr(c.indexOf(":")+3));this.img.src=c;this.boxcolor="#F95";var k=this;this.img.onload=function(){g&&g(this);k.trace("Image loaded, size: "+k.img.width+"x"+k.img.height); +this.dirty=!0;k.boxcolor="#9F9";k.setDirtyCanvas(!0)};this.img.onerror=function(){console.log("error loading the image:"+c)}}};m.prototype.onWidget=function(c,e){"load"==e.name&&this.loadImage(this.properties.url)};m.prototype.onDropFile=function(c){var e=this;this._url&&URL.revokeObjectURL(this._url);this._url=URL.createObjectURL(c);this.properties.url=this._url;this.loadImage(this._url,function(c){e.size[1]=c.height/c.width*e.size[0]})};e.registerNodeType("graphics/image",m);n.title="Palette";n.desc= +"Generates a color";n.prototype.onExecute=function(){var c=[];null!=this.properties.colorA&&c.push(hex2num(this.properties.colorA));null!=this.properties.colorB&&c.push(hex2num(this.properties.colorB));null!=this.properties.colorC&&c.push(hex2num(this.properties.colorC));null!=this.properties.colorD&&c.push(hex2num(this.properties.colorD));var e=this.getInputData(0);null==e&&(e=0.5);1e&&(e=0);if(0!=c.length){var g=[0,0,0];if(0==e)g=c[0];else if(1==e)g=c[c.length-1];else{var p=(c.length-1)* +e,e=c[Math.floor(p)],c=c[Math.floor(p)+1],p=p-Math.floor(p);g[0]=e[0]*(1-p)+c[0]*p;g[1]=e[1]*(1-p)+c[1]*p;g[2]=e[2]*(1-p)+c[2]*p}for(var k in g)g[k]/=255;this.boxcolor=colorToString(g);this.setOutputData(0,g)}};e.registerNodeType("color/palette",n);k.title="Frame";k.desc="Frame viewerew";k.widgets=[{name:"resize",text:"Resize box",type:"button"},{name:"view",text:"View Image",type:"button"}];k.prototype.onDrawBackground=function(c){this.frame&&!this.flags.collapsed&&c.drawImage(this.frame,0,0,this.size[0], +this.size[1])};k.prototype.onExecute=function(){this.frame=this.getInputData(0);this.setDirtyCanvas(!0)};k.prototype.onWidget=function(c,e){if("resize"==e.name&&this.frame){var g=this.frame.width,p=this.frame.height;g||null==this.frame.videoWidth||(g=this.frame.videoWidth,p=this.frame.videoHeight);g&&p&&(this.size=[g,p]);this.setDirtyCanvas(!0,!0)}else"view"==e.name&&this.show()};k.prototype.show=function(){showElement&&this.frame&&showElement(this.frame)};e.registerNodeType("graphics/frame",k);x.title= "Image fade";x.desc="Fades between images";x.widgets=[{name:"resizeA",text:"Resize to A",type:"button"},{name:"resizeB",text:"Resize to B",type:"button"}];x.prototype.onAdded=function(){this.createCanvas();var c=this.canvas.getContext("2d");c.fillStyle="#000";c.fillRect(0,0,this.properties.width,this.properties.height)};x.prototype.createCanvas=function(){this.canvas=document.createElement("canvas");this.canvas.width=this.properties.width;this.canvas.height=this.properties.height};x.prototype.onExecute= -function(){var c=this.canvas.getContext("2d");this.canvas.width=this.canvas.width;var e=this.getInputData(0);null!=e&&c.drawImage(e,0,0,this.canvas.width,this.canvas.height);e=this.getInputData(2);null==e?e=this.properties.fade:this.properties.fade=e;c.globalAlpha=e;e=this.getInputData(1);null!=e&&c.drawImage(e,0,0,this.canvas.width,this.canvas.height);c.globalAlpha=1;this.setOutputData(0,this.canvas);this.setDirtyCanvas(!0)};g.registerNodeType("graphics/imagefade",x);e.title="Crop";e.desc="Crop Image"; -e.prototype.onAdded=function(){this.createCanvas()};e.prototype.createCanvas=function(){this.canvas=document.createElement("canvas");this.canvas.width=this.properties.width;this.canvas.height=this.properties.height};e.prototype.onExecute=function(){var c=this.getInputData(0);c&&(c.width?(this.canvas.getContext("2d").drawImage(c,-this.properties.x,-this.properties.y,c.width*this.properties.scale,c.height*this.properties.scale),this.setOutputData(0,this.canvas)):this.setOutputData(0,null))};e.prototype.onDrawBackground= -function(c){this.flags.collapsed||this.canvas&&c.drawImage(this.canvas,0,0,this.canvas.width,this.canvas.height,0,0,this.size[0],this.size[1])};e.prototype.onPropertyChanged=function(c,e){this.properties[c]=e;"scale"==c?(this.properties[c]=parseFloat(e),0==this.properties[c]&&(this.trace("Error in scale"),this.properties[c]=1)):this.properties[c]=parseInt(e);this.createCanvas();return!0};g.registerNodeType("graphics/cropImage",e);C.title="Canvas";C.desc="Canvas to render stuff";C.prototype.onExecute= -function(){var c=this.canvas,e=this.properties.width|0,g=this.properties.height|0;c.width!=e&&(c.width=e);c.height!=g&&(c.height=g);this.properties.autoclear&&this.ctx.clearRect(0,0,c.width,c.height);this.setOutputData(0,c)};C.prototype.onAction=function(c,e){"clear"==c&&this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height)};g.registerNodeType("graphics/canvas",C);z.title="DrawImage";z.desc="Draws image into a canvas";z.prototype.onExecute=function(){var c=this.getInputData(0);if(c){var e= -this.getInputOrProperty("img");if(e){var g=this.getInputOrProperty("x"),s=this.getInputOrProperty("y");c.getContext("2d").drawImage(e,g,s)}}};g.registerNodeType("graphics/drawImage",z);v.title="DrawRectangle";v.desc="Draws rectangle in canvas";v.prototype.onExecute=function(){var c=this.getInputData(0);if(c){var e=this.getInputOrProperty("x"),g=this.getInputOrProperty("y"),s=this.getInputOrProperty("w"),p=this.getInputOrProperty("h");c.getContext("2d").fillRect(e,g,s,p)}};g.registerNodeType("graphics/drawRectangle", -v);D.title="Video";D.desc="Video playback";D.widgets=[{name:"play",text:"PLAY",type:"minibutton"},{name:"stop",text:"STOP",type:"minibutton"},{name:"demo",text:"Demo video",type:"button"},{name:"mute",text:"Mute video",type:"button"}];D.prototype.onExecute=function(){if(this.properties.url&&(this.properties.url!=this._video_url&&this.loadVideo(this.properties.url),this._video&&0!=this._video.width)){var c=this.getInputData(0);c&&0<=c&&1>=c&&(this._video.currentTime=c*this._video.duration,this._video.pause()); -this._video.dirty=!0;this.setOutputData(0,this._video);this.setOutputData(1,this._video.currentTime);this.setOutputData(2,this._video.duration);this.setDirtyCanvas(!0)}};D.prototype.onStart=function(){this.play()};D.prototype.onStop=function(){this.stop()};D.prototype.loadVideo=function(c){this._video_url=c;this.properties.use_proxy&&"http"==c.substr(0,4)&&g.proxy&&(c=g.proxy+c.substr(c.indexOf(":")+3));this._video=document.createElement("video");this._video.src=c;this._video.type="type=video/mp4"; -this._video.muted=!0;this._video.autoplay=!0;var e=this;this._video.addEventListener("loadedmetadata",function(c){e.trace("Duration: "+this.duration+" seconds");e.trace("Size: "+this.videoWidth+","+this.videoHeight);e.setDirtyCanvas(!0);this.width=this.videoWidth;this.height=this.videoHeight});this._video.addEventListener("progress",function(c){});this._video.addEventListener("error",function(c){console.log("Error loading video: "+this.src);e.trace("Error loading video: "+this.src);if(this.error)switch(this.error.code){case this.error.MEDIA_ERR_ABORTED:e.trace("You stopped the video."); -break;case this.error.MEDIA_ERR_NETWORK:e.trace("Network error - please try again later.");break;case this.error.MEDIA_ERR_DECODE:e.trace("Video is broken..");break;case this.error.MEDIA_ERR_SRC_NOT_SUPPORTED:e.trace("Sorry, your browser can't play this video.")}});this._video.addEventListener("ended",function(c){e.trace("Ended.");this.play()})};D.prototype.onPropertyChanged=function(c,e){this.properties[c]=e;"url"==c&&""!=e&&this.loadVideo(e);return!0};D.prototype.play=function(){this._video&&this._video.play()}; -D.prototype.playPause=function(){this._video&&(this._video.paused?this.play():this.pause())};D.prototype.stop=function(){this._video&&(this._video.pause(),this._video.currentTime=0)};D.prototype.pause=function(){this._video&&(this.trace("Video paused"),this._video.pause())};D.prototype.onWidget=function(c,e){};g.registerNodeType("graphics/video",D);B.title="Webcam";B.desc="Webcam image";B.is_webcam_open=!1;B.prototype.openStream=function(){function c(g){console.log("Webcam rejected",g);e._webcam_stream= +function(){var c=this.canvas.getContext("2d");this.canvas.width=this.canvas.width;var e=this.getInputData(0);null!=e&&c.drawImage(e,0,0,this.canvas.width,this.canvas.height);e=this.getInputData(2);null==e?e=this.properties.fade:this.properties.fade=e;c.globalAlpha=e;e=this.getInputData(1);null!=e&&c.drawImage(e,0,0,this.canvas.width,this.canvas.height);c.globalAlpha=1;this.setOutputData(0,this.canvas);this.setDirtyCanvas(!0)};e.registerNodeType("graphics/imagefade",x);g.title="Crop";g.desc="Crop Image"; +g.prototype.onAdded=function(){this.createCanvas()};g.prototype.createCanvas=function(){this.canvas=document.createElement("canvas");this.canvas.width=this.properties.width;this.canvas.height=this.properties.height};g.prototype.onExecute=function(){var c=this.getInputData(0);c&&(c.width?(this.canvas.getContext("2d").drawImage(c,-this.properties.x,-this.properties.y,c.width*this.properties.scale,c.height*this.properties.scale),this.setOutputData(0,this.canvas)):this.setOutputData(0,null))};g.prototype.onDrawBackground= +function(c){this.flags.collapsed||this.canvas&&c.drawImage(this.canvas,0,0,this.canvas.width,this.canvas.height,0,0,this.size[0],this.size[1])};g.prototype.onPropertyChanged=function(c,e){this.properties[c]=e;"scale"==c?(this.properties[c]=parseFloat(e),0==this.properties[c]&&(this.trace("Error in scale"),this.properties[c]=1)):this.properties[c]=parseInt(e);this.createCanvas();return!0};e.registerNodeType("graphics/cropImage",g);C.title="Canvas";C.desc="Canvas to render stuff";C.prototype.onExecute= +function(){var c=this.canvas,e=this.properties.width|0,g=this.properties.height|0;c.width!=e&&(c.width=e);c.height!=g&&(c.height=g);this.properties.autoclear&&this.ctx.clearRect(0,0,c.width,c.height);this.setOutputData(0,c)};C.prototype.onAction=function(c,e){"clear"==c&&this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height)};e.registerNodeType("graphics/canvas",C);z.title="DrawImage";z.desc="Draws image into a canvas";z.prototype.onExecute=function(){var c=this.getInputData(0);if(c){var e= +this.getInputOrProperty("img");if(e){var g=this.getInputOrProperty("x"),p=this.getInputOrProperty("y");c.getContext("2d").drawImage(e,g,p)}}};e.registerNodeType("graphics/drawImage",z);v.title="DrawRectangle";v.desc="Draws rectangle in canvas";v.prototype.onExecute=function(){var c=this.getInputData(0);if(c){var e=this.getInputOrProperty("x"),g=this.getInputOrProperty("y"),p=this.getInputOrProperty("w"),k=this.getInputOrProperty("h");c.getContext("2d").fillRect(e,g,p,k)}};e.registerNodeType("graphics/drawRectangle", +v);E.title="Video";E.desc="Video playback";E.widgets=[{name:"play",text:"PLAY",type:"minibutton"},{name:"stop",text:"STOP",type:"minibutton"},{name:"demo",text:"Demo video",type:"button"},{name:"mute",text:"Mute video",type:"button"}];E.prototype.onExecute=function(){if(this.properties.url&&(this.properties.url!=this._video_url&&this.loadVideo(this.properties.url),this._video&&0!=this._video.width)){var c=this.getInputData(0);c&&0<=c&&1>=c&&(this._video.currentTime=c*this._video.duration,this._video.pause()); +this._video.dirty=!0;this.setOutputData(0,this._video);this.setOutputData(1,this._video.currentTime);this.setOutputData(2,this._video.duration);this.setDirtyCanvas(!0)}};E.prototype.onStart=function(){this.play()};E.prototype.onStop=function(){this.stop()};E.prototype.loadVideo=function(c){this._video_url=c;this.properties.use_proxy&&"http"==c.substr(0,4)&&e.proxy&&(c=e.proxy+c.substr(c.indexOf(":")+3));this._video=document.createElement("video");this._video.src=c;this._video.type="type=video/mp4"; +this._video.muted=!0;this._video.autoplay=!0;var g=this;this._video.addEventListener("loadedmetadata",function(c){g.trace("Duration: "+this.duration+" seconds");g.trace("Size: "+this.videoWidth+","+this.videoHeight);g.setDirtyCanvas(!0);this.width=this.videoWidth;this.height=this.videoHeight});this._video.addEventListener("progress",function(c){});this._video.addEventListener("error",function(c){console.log("Error loading video: "+this.src);g.trace("Error loading video: "+this.src);if(this.error)switch(this.error.code){case this.error.MEDIA_ERR_ABORTED:g.trace("You stopped the video."); +break;case this.error.MEDIA_ERR_NETWORK:g.trace("Network error - please try again later.");break;case this.error.MEDIA_ERR_DECODE:g.trace("Video is broken..");break;case this.error.MEDIA_ERR_SRC_NOT_SUPPORTED:g.trace("Sorry, your browser can't play this video.")}});this._video.addEventListener("ended",function(c){g.trace("Ended.");this.play()})};E.prototype.onPropertyChanged=function(c,e){this.properties[c]=e;"url"==c&&""!=e&&this.loadVideo(e);return!0};E.prototype.play=function(){this._video&&this._video.play()}; +E.prototype.playPause=function(){this._video&&(this._video.paused?this.play():this.pause())};E.prototype.stop=function(){this._video&&(this._video.pause(),this._video.currentTime=0)};E.prototype.pause=function(){this._video&&(this.trace("Video paused"),this._video.pause())};E.prototype.onWidget=function(c,e){};e.registerNodeType("graphics/video",E);B.title="Webcam";B.desc="Webcam image";B.is_webcam_open=!1;B.prototype.openStream=function(){function c(g){console.log("Webcam rejected",g);e._webcam_stream= !1;B.is_webcam_open=!1;e.boxcolor="red";e.trigger("stream_error")}if(navigator.getUserMedia){this._waiting_confirmation=!0;navigator.mediaDevices.getUserMedia({audio:!1,video:{facingMode:this.properties.facingMode}}).then(this.streamReady.bind(this))["catch"](c);var e=this}};B.prototype.closeStream=function(){if(this._webcam_stream){var c=this._webcam_stream.getTracks();if(c.length)for(var e=0;e=this.size[1]||!this.properties.show||!this._video||(c.save(),c.drawImage(this._video,0,0,this.size[0],this.size[1]),c.restore())};B.prototype.onGetOutputs=function(){return[["width","number"],["height","number"],["stream_ready",g.EVENT],["stream_closed",g.EVENT],["stream_error",g.EVENT]]}; -g.registerNodeType("graphics/webcam",B)})(this); -(function(y){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 m(){this.addInput("Texture","Texture");this.properties={flipY:!1};this.size=[c.image_preview_size,c.image_preview_size]}function n(){this.addInput("Texture","Texture");this.addOutput("tex","Texture");this.addOutput("name","string");this.properties={name:"",generate_mipmaps:!1}}function l(){this.addInput("Texture", +function(c){var e=this;return[{content:e.properties.show?"Hide Frame":"Show Frame",callback:function(){e.properties.show=!e.properties.show}}]};B.prototype.onDrawBackground=function(c){this.flags.collapsed||20>=this.size[1]||!this.properties.show||!this._video||(c.save(),c.drawImage(this._video,0,0,this.size[0],this.size[1]),c.restore())};B.prototype.onGetOutputs=function(){return[["width","number"],["height","number"],["stream_ready",e.EVENT],["stream_closed",e.EVENT],["stream_error",e.EVENT]]}; +e.registerNodeType("graphics/webcam",B)})(this); +(function(y){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 m(){this.addInput("Texture","Texture");this.properties={flipY:!1};this.size=[c.image_preview_size,c.image_preview_size]}function n(){this.addInput("Texture","Texture");this.addOutput("tex","Texture");this.addOutput("name","string");this.properties={name:"",generate_mipmaps:!1}}function k(){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 x(){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 e(){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 C(){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 z(){this.addInput("Texture","Texture");this.properties={additive:!1,antialiasing:!1,filter:!0,disable_alpha:!1,gamma:1,viewport:[0,0,1,1]};this.size[0]=130}function v(){this.addInput("Texture","Texture");this.addOutput("","Texture");this.properties={size:0,generate_mipmaps:!1,precision:c.DEFAULT}}function D(){this.addInput("Texture","Texture");this.addOutput("","Texture");this.properties={iterations:1,generate_mipmaps:!1,precision:c.DEFAULT}} -function B(){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 g(){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:!0}; -this._uniforms={u_texture:0};this._max=new Float32Array(4);this._min=new Float32Array(4);this._textures_chain=[]}function r(){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 q(){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 A(){this.addInput("Image","image");this.addOutput("","Texture");this.properties={}}function s(){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};s._shader||(s._shader=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,s.pixel_shader))}function p(){this.addInput("Texture", -"Texture");this.addOutput("R","Texture");this.addOutput("G","Texture");this.addOutput("B","Texture");this.addOutput("A","Texture");p._shader||(p._shader=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,p.pixel_shader))}function t(){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 h(){this.addOutput("Texture","Texture");this._tex_color=vec4.create();this.properties={color:vec4.create(),precision:c.DEFAULT}}function a(){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};a._shader||(a._shader=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,a.pixel_shader));this._uniforms={u_angle:0,u_colorA:vec3.create(),u_colorB:vec3.create()}}function b(){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 d(){this.addInput("Tex.","Texture");this.addOutput("Edges","Texture");this.properties={invert:!0,threshold:!1,factor:1,precision:c.DEFAULT};d._shader||(d._shader=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,d.pixel_shader))}function k(){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 f(){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:1E4}}function K(){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 w(){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 H(){this.addInput("Texture","Texture");this.addOutput("Filtered","Texture");this.properties={intensity:1,radius:5}}function I(){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 G(){this.addOutput("Webcam","Texture");this.properties={texture_name:"",facingMode:"user"};this.boxcolor="black";this.version=0}function E(){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};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 M(){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 J(){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 L(){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 N(){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 O(){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=y.LiteGraph;y.LGraphTexture=null;"undefined"!= -typeof GL&&(LGraphCanvas.link_type_colors.Texture="#987",y.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";var f=null;switch(d){case c.LOW:f=gl.UNSIGNED_BYTE;break;case c.HIGH:f=gl.HIGH_PRECISION_FORMAT; -break;case c.REUSE:return a;default:f=a?a.type:gl.UNSIGNED_BYTE}b&&b.width==a.width&&b.height==a.height&&b.type==f||(b=new GL.Texture(a.width,a.height,{type:f,format:gl.RGBA,filter:gl.LINEAR}));return b},c.getTextureType=function(a,b){var d=b?b.type:gl.UNSIGNED_BYTE;switch(a){case c.HIGH:d=gl.HIGH_PRECISION_FORMAT;break;case c.LOW:d=gl.UNSIGNED_BYTE}return d},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){if(a){var c=null;"string"==typeof a?c=GL.Texture.fromURL(a):-1!=b.toLowerCase().indexOf(".dds")?c=GL.Texture.fromDDSInMemory(a):(a=new Blob([d]), -a=URL.createObjectURL(a),c=GL.Texture.fromURL(a));this._drop_texture=c;this.properties.name=b}else 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.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),m.title="Preview",m.desc="Show a texture in the graph canvas",m.allow_preview=!1,m.prototype.onDrawBackground= -function(a){if(!this.flags.collapsed&&(a.webgl||m.allow_preview)){var b=this.getInputData(0);if(b){var d=null,d=!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(d,0,0,this.size[0],this.size[1]);a.restore()}}},F.registerNodeType("texture/preview",m),n.title="Save",n.desc="Save a texture in the repository",n.prototype.getPreviewTexture=function(){return this._texture},n.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",n),l.widgets_info={uvcode:{widget:"code"},pixelcode:{widget:"code"},precision:{widget:"combo", -values:c.MODE_VALUES}},l.title="Operation",l.desc="Texture shader operation",l.presets={},l.prototype.getExtraMenuOptions=function(a){var b=this;return[{content:b.properties.show?"Hide Texture":"Show Texture",callback:function(){b.properties.show=!b.properties.show}}]},l.prototype.onPropertyChanged=function(){this.has_error=!1},l.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())},l.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 h=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:h,format:gl.RGBA,filter:gl.LINEAR});h="";this.properties.uvcode&&(h="uv = "+this.properties.uvcode,-1!=this.properties.uvcode.indexOf(";")&&(h=this.properties.uvcode));var k="";this.properties.pixelcode&&(k="result = "+this.properties.pixelcode,-1!=this.properties.pixelcode.indexOf(";")&&(k=this.properties.pixelcode));var e=this._shader;if(!(this.has_error||e&&this._shader_code==h+"|"+k)){var g=c.replaceCode(l.pixel_shader,{UV_CODE:h,PIXEL_CODE:k});try{e=new GL.Shader(Shader.SCREEN_VERTEX_SHADER, -g),this.boxcolor="#00FF00"}catch(p){GL.Shader.dumpErrorToConsole(p,Shader.SCREEN_VERTEX_SHADER,g);this.boxcolor="#FF0000";this.has_error=!0;return}this._shader=e;this._shader_code=h+"|"+k}if(this._shader){var s=this.getInputData(2);null!=s?this.properties.value=s:s=parseFloat(this.properties.value);var n=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();e.uniforms({u_texture:0, -u_textureB:1,value:s,texSize:[d,f],time:n}).draw(c)});this.setOutputData(0,this._tex)}}}},l.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", -l.registerPreset=function(a,b){l.presets[a]=b},l.registerPreset("",""),l.registerPreset("bypass","color"),l.registerPreset("add","color + colorB * value"),l.registerPreset("substract","(color - colorB) * value"),l.registerPreset("mate","mix( color, colorB, color4B.a * value)"),l.registerPreset("invert","vec3(1.0) - color"),l.registerPreset("multiply","color * colorB * value"),l.registerPreset("divide","(color / colorB) / value"),l.registerPreset("difference","abs(color - colorB) * value"),l.registerPreset("max", -"max(color, colorB) * value"),l.registerPreset("min","min(color, colorB) * value"),l.registerPreset("displace","texture2D(u_texture, uv + (colorB.xy - vec2(0.5)) * value).xyz"),l.registerPreset("grayscale","vec3(color.x + color.y + color.z) * value / 3.0"),l.registerPreset("saturation","mix( vec3(color.x + color.y + color.z) / 3.0, color, value )"),l.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)"), -l.prototype.onInspect=function(a){var b=this;a.addCombo("Presets","",{values:Object.keys(l.presets),callback:function(d){var c=l.presets[d];c&&(b.setProperty("pixelcode",c),b.title=d,a.refresh())}})},F.registerNodeType("texture/operation",l),x.title="Shader",x.desc="Texture shader",x.widgets_info={code:{type:"code"},precision:{widget:"combo",values:c.MODE_VALUES}},x.prototype.onPropertyChanged=function(a,b){if("code"==a){var d=this.getShader();if(d){var c=d.uniformInfo;if(this.inputs)for(var f={}, -h=0;hb;++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){if(a){var c=null;"string"==typeof a?c=GL.Texture.fromURL(a):-1!=b.toLowerCase().indexOf(".dds")?c=GL.Texture.fromDDSInMemory(a):(a=new Blob([d]),a=URL.createObjectURL(a),c=GL.Texture.fromURL(a));this._drop_texture=c;this.properties.name=b}else 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.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),m.title="Preview",m.desc="Show a texture in the graph canvas",m.allow_preview=!1,m.prototype.onDrawBackground=function(a){if(!this.flags.collapsed&&(a.webgl||m.allow_preview)){var b=this.getInputData(0);if(b){var d=null,d=!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(d,0,0,this.size[0],this.size[1]);a.restore()}}}, +F.registerNodeType("texture/preview",m),n.title="Save",n.desc="Save a texture in the repository",n.prototype.getPreviewTexture=function(){return this._texture},n.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",n),k.widgets_info={uvcode:{widget:"code"},pixelcode:{widget:"code"},precision:{widget:"combo",values:c.MODE_VALUES}},k.title="Operation",k.desc="Texture shader operation",k.presets={},k.prototype.getExtraMenuOptions=function(a){var b=this;return[{content:b.properties.show?"Hide Texture":"Show Texture",callback:function(){b.properties.show=!b.properties.show}}]},k.prototype.onPropertyChanged= +function(){this.has_error=!1},k.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())},k.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 h=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:h,format:gl.RGBA,filter:gl.LINEAR});h="";this.properties.uvcode&&(h="uv = "+this.properties.uvcode,-1!=this.properties.uvcode.indexOf(";")&&(h=this.properties.uvcode));var e="";this.properties.pixelcode&&(e="result = "+this.properties.pixelcode, +-1!=this.properties.pixelcode.indexOf(";")&&(e=this.properties.pixelcode));var g=this._shader;if(!(this.has_error||g&&this._shader_code==h+"|"+e)){var l=c.replaceCode(k.pixel_shader,{UV_CODE:h,PIXEL_CODE:e});try{g=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,l),this.boxcolor="#00FF00"}catch(p){GL.Shader.dumpErrorToConsole(p,Shader.SCREEN_VERTEX_SHADER,l);this.boxcolor="#FF0000";this.has_error=!0;return}this._shader=g;this._shader_code=h+"|"+e}if(this._shader){var r=this.getInputData(2);null!=r?this.properties.value= +r:r=parseFloat(this.properties.value);var s=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();g.uniforms({u_texture:0,u_textureB:1,value:r,texSize:[d,f],time:s}).draw(c)});this.setOutputData(0,this._tex)}}}},k.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", +k.registerPreset=function(a,b){k.presets[a]=b},k.registerPreset("",""),k.registerPreset("bypass","color"),k.registerPreset("add","color + colorB * value"),k.registerPreset("substract","(color - colorB) * value"),k.registerPreset("mate","mix( color, colorB, color4B.a * value)"),k.registerPreset("invert","vec3(1.0) - color"),k.registerPreset("multiply","color * colorB * value"),k.registerPreset("divide","(color / colorB) / value"),k.registerPreset("difference","abs(color - colorB) * value"),k.registerPreset("max", +"max(color, colorB) * value"),k.registerPreset("min","min(color, colorB) * value"),k.registerPreset("displace","texture2D(u_texture, uv + (colorB.xy - vec2(0.5)) * value).xyz"),k.registerPreset("grayscale","vec3(color.x + color.y + color.z) * value / 3.0"),k.registerPreset("saturation","mix( vec3(color.x + color.y + color.z) / 3.0, color, value )"),k.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)"), +k.prototype.onInspect=function(a){var b=this;a.addCombo("Presets","",{values:Object.keys(k.presets),callback:function(d){var c=k.presets[d];c&&(b.setProperty("pixelcode",c),b.title=d,a.refresh())}})},F.registerNodeType("texture/operation",k),x.title="Shader",x.desc="Texture shader",x.widgets_info={code:{type:"code",lang:"glsl"},precision:{widget:"combo",values:c.MODE_VALUES}},x.prototype.onPropertyChanged=function(a,b){if("code"==a){var d=this.getShader();if(d){var c=d.uniformInfo;if(this.inputs)for(var f= +{},h=0;h 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", z.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",z),v.title="Copy",v.desc="Copy Texture",v.widgets_info={size:{widget:"combo",values:[0,32,64,128,256,512,1024,2048]},precision:{widget:"combo", values:c.MODE_VALUES}},v.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,h=a.type;this.properties.precision===c.LOW?h=gl.UNSIGNED_BYTE:this.properties.precision===c.HIGH&&(h=gl.HIGH_PRECISION_FORMAT);f&&f.width==b&&f.height==d&&f.type==h||(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:h,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",v),D.title="Downsample",D.desc="Downsample Texture",D.widgets_info={iterations:{type:"number",step:1,precision:0,min:0},precision:{widget:"combo",values:c.MODE_VALUES}}, -D.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=D._shader;b||(D._shader=b=new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER,D.pixel_shader));var d=a.width|0,f=a.height|0,h=a.type;this.properties.precision===c.LOW?h=gl.UNSIGNED_BYTE:this.properties.precision===c.HIGH&&(h=gl.HIGH_PRECISION_FORMAT);var k=this.properties.iterations||1,e=a,g= -null,p=[],a={type:h,format:a.format},h=vec2.create(),s={u_offset:h};this._texture&&GL.Texture.releaseTemporary(this._texture);for(var l=0;l>1||0;f=f>>1||0;g=GL.Texture.getTemporary(d,f,a);p.push(g);e.setParameter(GL.TEXTURE_MAG_FILTER,GL.NEAREST);e.copyTo(g,b,s);if(1==d&&1==f)break;e=g}this._texture=p.pop();for(l=0;lthis.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,h=a.type;this.properties.precision===c.LOW?h=gl.UNSIGNED_BYTE:this.properties.precision===c.HIGH&&(h=gl.HIGH_PRECISION_FORMAT);var e=this.properties.iterations||1,g=a,l= +null,p=[],a={type:h,format:a.format},h=vec2.create(),r={u_offset:h};this._texture&&GL.Texture.releaseTemporary(this._texture);for(var k=0;k>1||0;f=f>>1||0;l=GL.Texture.getTemporary(d,f,a);p.push(l);g.setParameter(GL.TEXTURE_MAG_FILTER,GL.NEAREST);g.copyTo(l,b,r);if(1==d&&1==f)break;g=l}this._texture=p.pop();for(k=0;k>=2,c++,1!=d););a.copyTo(this._textures_chain[0]);for(var c=1;c<=this._textures_chain.length;++c)a=this._textures_chain[c];var f=g._shader,h=this._uniforms;h.u_mipmap_offset=this.properties.mipmap_offset;gl.disable(gl.DEPTH_TEST); -gl.disable(gl.BLEND);this._temp_texture.drawTo(function(){a.toViewport(f,h)})}},g.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", -r.title="Smooth",r.desc="Smooth texture over time",r.prototype.onExecute=function(){var a=this.getInputData(0);if(a&&this.isOutputConnected(0)){r._shader||(r._shader=new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER,r.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));var b= -this._temp_texture,d=this._temp_texture2,c=r._shader,f=this._uniforms;f.u_factor=1-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}},r.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",r),q.title="Lineal Avg Smooth",q.desc="Smooth texture linearly over time",q["@samples"]={type:"number",min:1,max:64,step:1,precision:1},q.prototype.getPreviewTexture=function(){return this._temp_texture2},q.prototype.onExecute=function(){var a=this.getInputData(0);if(a&&this.isOutputConnected(0)){q._shader||(q._shader_copy=new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER,q.pixel_shader_copy),q._shader_avg=new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER,q.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,h=this._temp_texture2,k=q._shader_copy,e=q._shader_avg,g=this._uniforms;g.u_samples=b;g.u_isamples=1/b;gl.disable(gl.BLEND);gl.disable(gl.DEPTH_TEST); -f.drawTo(function(){h.bind(1);a.toViewport(k,g)});this._temp_texture_out.drawTo(function(){f.toViewport(e,g)});this.setOutputData(0,this._temp_texture_out);this._temp_texture=h;this._temp_texture2=f}else this.setOutputData(0,this._temp_texture_out);this.setOutputData(1,this._temp_texture2);this.frame++}},q.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", -q.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", -q),A.title="Image to Texture",A.desc="Uploads an image to the GPU",A.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(f){console.error("image comes from an unsafe location, cannot be uploaded to webgl: "+f); -return}this.setOutputData(0,this._temp_texture)}}},F.registerNodeType("texture/imageToTexture",A),s.widgets_info={texture:{widget:"texture"},precision:{widget:"combo",values:c.MODE_VALUES}},s.title="LUT",s.desc="Apply LUT to Texture",s.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(s._shader,{u_texture:0,u_textureB:1, -u_amount:d})});this.setOutputData(0,this._tex)}else this.setOutputData(0,a)}}},s.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",s),p.title="Texture to Channels",p.desc="Split texture channels",p.prototype.onExecute=function(){var a=this.getInputData(0);if(a){this._channels||(this._channels=Array(4));for(var b=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);for(var f=Mesh.getScreenQuad(),h=p._shader,k=[[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]],c=0;4>c;c++)this._channels[c]&&(this._channels[c].drawTo(function(){a.bind(0);h.uniforms({u_texture:0,u_mask:k[c]}).draw(f)}),this.setOutputData(c,this._channels[c]))}}},p.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",p),t.title="Channels to Texture",t.desc="Split texture channels",t.widgets_info={precision:{widget:"combo",values:c.MODE_VALUES}},t.prototype.onExecute=function(){var a=c.getWhiteTexture(),b=this.getInputData(0)||a,d=this.getInputData(1)||a,f=this.getInputData(2)||a,h=this.getInputData(3)||a;gl.disable(gl.BLEND);gl.disable(gl.DEPTH_TEST);var k=Mesh.getScreenQuad();t._shader||(t._shader=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,t.pixel_shader));var e=t._shader, -a=Math.max(b.width,d.width,f.width,h.width),g=Math.max(b.height,d.height,f.height,h.height),p=this.properties.precision==c.HIGH?c.HIGH_PRECISION_FORMAT:gl.UNSIGNED_BYTE;this._texture&&this._texture.width==a&&this._texture.height==g&&this._texture.type==p||(this._texture=new GL.Texture(a,g,{type:p,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 s=this._uniforms;this._texture.drawTo(function(){b.bind(0); -d.bind(1);f.bind(2);h.bind(3);e.uniforms(s).draw(k)});this.setOutputData(0,this._texture)},t.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",t),h.title="Color",h.desc="Generates a 1x1 texture with a constant color",h.widgets_info={precision:{widget:"combo",values:c.MODE_VALUES}},h.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])},h.prototype.onExecute= +F.registerNodeType("texture/average",B),e.widgets_info={mode:{widget:"combo",values:["min","max","avg"]}},e.title="MinMax",e.desc="Compute the scene min max",e.prototype.onExecute=function(){this.properties.use_previous_frame||this.update();this.setOutputData(0,this._temp_texture);this.setOutputData(1,this._luminance)},e.prototype.onPreRenderExecute=function(){this.update()},e.prototype.update=function(){var a=this.getInputData(0);if(a&&(this.isOutputConnected(0)||this.isOutputConnected(1))){e._shader|| +(e._shader=new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER,e.pixel_shader));var b=gl.UNSIGNED_BYTE;a.type!=b&&(b=gl.FLOAT);var d=512;if(!this._textures_chain.length||this._textures_chain[0].type!=b)for(;c&&(this._textures_chain[c]=new GL.Texture(d,d,{type:b,format:gl.RGBA,filter:gl.NEAREST}),d>>=2,c++,1!=d););a.copyTo(this._textures_chain[0]);for(var c=1;c<=this._textures_chain.length;++c)a=this._textures_chain[c];var f=e._shader,h=this._uniforms;h.u_mipmap_offset=this.properties.mipmap_offset;gl.disable(gl.DEPTH_TEST); +gl.disable(gl.BLEND);this._temp_texture.drawTo(function(){a.toViewport(f,h)})}},e.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", +q.title="Smooth",q.desc="Smooth texture over time",q.prototype.onExecute=function(){var a=this.getInputData(0);if(a&&this.isOutputConnected(0)){q._shader||(q._shader=new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER,q.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));var b= +this._temp_texture,d=this._temp_texture2,c=q._shader,f=this._uniforms;f.u_factor=1-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}},q.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",q),t.title="Lineal Avg Smooth",t.desc="Smooth texture linearly over time",t["@samples"]={type:"number",min:1,max:64,step:1,precision:1},t.prototype.getPreviewTexture=function(){return this._temp_texture2},t.prototype.onExecute=function(){var a=this.getInputData(0);if(a&&this.isOutputConnected(0)){t._shader||(t._shader_copy=new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER,t.pixel_shader_copy),t._shader_avg=new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER,t.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,h=this._temp_texture2,e=t._shader_copy,g=t._shader_avg,l=this._uniforms;l.u_samples=b;l.u_isamples=1/b;gl.disable(gl.BLEND);gl.disable(gl.DEPTH_TEST); +f.drawTo(function(){h.bind(1);a.toViewport(e,l)});this._temp_texture_out.drawTo(function(){f.toViewport(g,l)});this.setOutputData(0,this._temp_texture_out);this._temp_texture=h;this._temp_texture2=f}else this.setOutputData(0,this._temp_texture_out);this.setOutputData(1,this._temp_texture2);this.frame++}},t.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", +t.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", +t),A.title="Image to Texture",A.desc="Uploads an image to the GPU",A.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(f){console.error("image comes from an unsafe location, cannot be uploaded to webgl: "+f); +return}this.setOutputData(0,this._temp_texture)}}},F.registerNodeType("texture/imageToTexture",A),p.widgets_info={texture:{widget:"texture"},precision:{widget:"combo",values:c.MODE_VALUES}},p.title="LUT",p.desc="Apply LUT to Texture",p.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(p._shader,{u_texture:0,u_textureB:1, +u_amount:d})});this.setOutputData(0,this._tex)}else this.setOutputData(0,a)}}},p.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",p),s.title="Texture to Channels",s.desc="Split texture channels",s.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);for(var f=Mesh.getScreenQuad(),h=s._shader,e=[[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]],c=0;4>c;c++)this._channels[c]&&(this._channels[c].drawTo(function(){a.bind(0);h.uniforms({u_texture:0,u_mask:e[c]}).draw(f)}),this.setOutputData(c,this._channels[c]))}}},s.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",s),w.title="Channels to Texture",w.desc="Split texture channels",w.widgets_info={precision:{widget:"combo",values:c.MODE_VALUES}},w.prototype.onExecute=function(){var a=c.getWhiteTexture(),b=this.getInputData(0)||a,d=this.getInputData(1)||a,f=this.getInputData(2)||a,h=this.getInputData(3)||a;gl.disable(gl.BLEND);gl.disable(gl.DEPTH_TEST);var e=Mesh.getScreenQuad();w._shader||(w._shader=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,w.pixel_shader));var g=w._shader, +a=Math.max(b.width,d.width,f.width,h.width),l=Math.max(b.height,d.height,f.height,h.height),p=this.properties.precision==c.HIGH?c.HIGH_PRECISION_FORMAT:gl.UNSIGNED_BYTE;this._texture&&this._texture.width==a&&this._texture.height==l&&this._texture.type==p||(this._texture=new GL.Texture(a,l,{type:p,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 r=this._uniforms;this._texture.drawTo(function(){b.bind(0); +d.bind(1);f.bind(2);h.bind(3);g.uniforms(r).draw(e)});this.setOutputData(0,this._texture)},w.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",w),l.title="Color",l.desc="Generates a 1x1 texture with a constant color",l.widgets_info={precision:{widget:"combo",values:c.MODE_VALUES}},l.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])},l.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;ba.width?d: -a,this._tex,this.properties.precision);gl.disable(gl.BLEND);gl.disable(gl.DEPTH_TEST);var k=Mesh.getScreenQuad(),e=null,g=this._uniforms;f?(e=b._shader_tex,e||(e=b._shader_tex=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,b.pixel_shader,{MIX_TEX:""}))):(e=b._shader_factor,e||(e=b._shader_factor=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,b.pixel_shader)),h=null==h?this.properties.factor:h,g.u_mix.set([h,h,h,h]));var p=this.properties.invert;this._tex.drawTo(function(){a.bind(p?1:0);d.bind(p?0:1);f&&f.bind(2); -e.uniforms(g).draw(k)});this.setOutputData(0,this._tex)}}},b.prototype.onGetInputs=function(){return[["factor","number"]]},b.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",b),d.title="Edges",d.desc="Detects edges",d.widgets_info={precision:{widget:"combo",values:c.MODE_VALUES}},d.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 b=Mesh.getScreenQuad(),f=d._shader,h=this.properties.invert,k=this.properties.factor, -e=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:k,u_threshold:e,u_invert:h?1:0}).draw(b)});this.setOutputData(0,this._tex)}}},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_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",d),k.title="Depth Range",k.desc="Generates a texture with a depth range",k.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 d=this._uniforms,b=this.properties.distance;this.isInputConnected(1)&&(b=this.getInputData(1),this.properties.distance=b);var c=this.properties.range;this.isInputConnected(2)&&(c=this.getInputData(2),this.properties.range=c);d.u_distance=b;d.u_range=c;gl.disable(gl.BLEND);gl.disable(gl.DEPTH_TEST);var f=Mesh.getScreenQuad();k._shader||(k._shader=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,k.pixel_shader),k._shader_onlydepth=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,k.pixel_shader, -{ONLY_DEPTH:""}));var h=this.properties.only_depth?k._shader_onlydepth:k._shader,b=null,b=a.near_far_planes?a.near_far_planes:window.LS&&LS.Renderer._main_camera?LS.Renderer._main_camera._uniforms.u_camera_planes:[0.1,1E3];d.u_camera_planes=b;this._temp_texture.drawTo(function(){a.bind(0);h.uniforms(d).draw(f)});this._temp_texture.near_far_planes=b;this.setOutputData(0,this._temp_texture)}}},k.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",k),f.widgets_info={precision:{widget:"combo",values:c.MODE_VALUES}},f.title="Linear Depth",f.desc="Creates a color texture with linear depth",f.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 h=Mesh.getScreenQuad();f._shader||(f._shader=new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER,f.pixel_shader));var k=f._shader,b=null,b=a.near_far_planes?a.near_far_planes:window.LS&&LS.Renderer._main_camera? -LS.Renderer._main_camera._uniforms.u_camera_planes:[0.1,1E3];d.u_camera_planes=b;this._temp_texture.drawTo(function(){a.bind(0);k.uniforms(d).draw(h)});this._temp_texture.near_far_planes=b;this.setOutputData(0,this._temp_texture)}}},f.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", +a,this._tex,this.properties.precision);gl.disable(gl.BLEND);gl.disable(gl.DEPTH_TEST);var e=Mesh.getScreenQuad(),g=null,l=this._uniforms;f?(g=b._shader_tex,g||(g=b._shader_tex=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,b.pixel_shader,{MIX_TEX:""}))):(g=b._shader_factor,g||(g=b._shader_factor=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,b.pixel_shader)),h=null==h?this.properties.factor:h,l.u_mix.set([h,h,h,h]));var p=this.properties.invert;this._tex.drawTo(function(){a.bind(p?1:0);d.bind(p?0:1);f&&f.bind(2); +g.uniforms(l).draw(e)});this.setOutputData(0,this._tex)}}},b.prototype.onGetInputs=function(){return[["factor","number"]]},b.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",b),d.title="Edges",d.desc="Detects edges",d.widgets_info={precision:{widget:"combo",values:c.MODE_VALUES}},d.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 b=Mesh.getScreenQuad(),f=d._shader,h=this.properties.invert,e=this.properties.factor, +g=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:e,u_threshold:g,u_invert:h?1:0}).draw(b)});this.setOutputData(0,this._tex)}}},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_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",d),h.title="Depth Range",h.desc="Generates a texture with a depth range",h.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 d=this._uniforms,b=this.properties.distance;this.isInputConnected(1)&&(b=this.getInputData(1),this.properties.distance=b);var c=this.properties.range;this.isInputConnected(2)&&(c=this.getInputData(2),this.properties.range=c);d.u_distance=b;d.u_range=c;gl.disable(gl.BLEND);gl.disable(gl.DEPTH_TEST);var f=Mesh.getScreenQuad();h._shader||(h._shader=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,h.pixel_shader),h._shader_onlydepth=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,h.pixel_shader, +{ONLY_DEPTH:""}));var e=this.properties.only_depth?h._shader_onlydepth:h._shader,b=null,b=a.near_far_planes?a.near_far_planes:window.LS&&LS.Renderer._main_camera?LS.Renderer._main_camera._uniforms.u_camera_planes:[0.1,1E3];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)}}},h.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",h),f.widgets_info={precision:{widget:"combo",values:c.MODE_VALUES}},f.title="Linear Depth",f.desc="Creates a color texture with linear depth",f.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 h=Mesh.getScreenQuad();f._shader||(f._shader=new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER,f.pixel_shader));var e=f._shader,b=null,b=a.near_far_planes?a.near_far_planes:window.LS&&LS.Renderer._main_camera? +LS.Renderer._main_camera._uniforms.u_camera_planes:[0.1,1E3];d.u_camera_planes=b;this._temp_texture.drawTo(function(){a.bind(0);e.uniforms(d).draw(h)});this._temp_texture.near_far_planes=b;this.setOutputData(0,this._temp_texture)}}},f.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",f),K.title="Blur",K.desc="Blur a texture",K.widgets_info={precision:{widget:"combo",values:c.MODE_VALUES}},K.max_iterations=20,K.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),K.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);var f=this.properties.preserve_aspect?f:1,h=this.properties.scale||[1,1];a.applyBlur(f*h[0],h[1],c,b);for(a=1;a>=1;1<(d|0)&&(d>>=1);if(2>b)break;p=e[t]=GL.Texture.getTemporary(b,d,f);n[0]=1/s.width;n[1]=1/s.height;s.blit(p,g.uniforms(k));s=p}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/s.width,n[1]=1/s.height,k.u_intensity=m,k.u_delta=1,s.blit(b,g.uniforms(k)),this.setOutputData(2,b));gl.enable(gl.BLEND);gl.blendFunc(gl.ONE,gl.ONE);k.u_intensity= -this.getInputOrProperty("persistence");k.u_delta=0.5;for(t-=2;0<=t;t--)p=e[t],e[t]=null,n[0]=1/s.width,n[1]=1/s.height,s.blit(p,g.uniforms(k)),GL.Texture.releaseTemporary(s),s=p;gl.disable(gl.BLEND);this.isOutputConnected(1)&&(e=this._glow_texture,e&&e.width==a.width&&e.height==a.height&&e.type==h&&e.format==a.format||(e=this._glow_texture=new GL.Texture(a.width,a.height,{type:h,format:a.format,filter:gl.LINEAR})),s.blit(e),this.setOutputData(1,e));if(this.isOutputConnected(0)){e=this._final_texture; -e&&e.width==a.width&&e.height==a.height&&e.type==h&&e.format==a.format||(e=this._final_texture=new GL.Texture(a.width,a.height,{type:h,format:a.format,filter:gl.LINEAR}));var q=this.getInputData(1),u=this.getInputOrProperty("dirt_factor");k.u_intensity=m;g=q?w._dirt_final_shader:w._final_shader;g||(g=q?w._dirt_final_shader=new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER,w.final_pixel_shader,{USE_DIRT:""}):w._final_shader=new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER,w.final_pixel_shader));e.drawTo(function(){a.bind(0); -s.bind(1);q&&(g.setUniform("u_dirt_factor",u),g.setUniform("u_dirt_texture",q.bind(2)));g.toViewport(k)});this.setOutputData(0,e)}GL.Texture.releaseTemporary(s)}},w.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}",w.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}", -w.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",w),H.title="Kuwahara Filter",H.desc="Filters a texture giving an artistic oil canvas painting",H.max_radius=10,H._shaders=[],H.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),H.max_radius);if(0== +b)}}},F.registerNodeType("texture/blur",K),r.title="Glow",r.desc="Filters a texture giving it a glow effect",r.weights=new Float32Array([0.5,0.4,0.3,0.2]),r.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}},r.prototype.onGetInputs=function(){return[["enabled","boolean"],["threshold","number"],["intensity","number"],["persistence","number"],["iterations","number"],["dirt_factor", +"number"]]},r.prototype.onGetOutputs=function(){return[["average","Texture"]]},r.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},h=c.getTextureType(this.properties.precision,a),e=this._uniforms,g=this._textures,l=r._cut_shader; +l||(l=r._cut_shader=new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER,r.cut_pixel_shader));gl.disable(gl.DEPTH_TEST);gl.disable(gl.BLEND);e.u_threshold=this.getInputOrProperty("threshold");var p=g[0]=GL.Texture.getTemporary(b,d,f);a.blit(p,l.uniforms(e));var k=p,s=this.getInputOrProperty("iterations"),s=Math.clamp(s,1,16)|0,n=e.u_texel_size,q=this.getInputOrProperty("intensity");e.u_intensity=1;e.u_delta=this.properties.scale;l=r._shader;l||(l=r._shader=new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER,r.scale_pixel_shader)); +for(var m=1;m>=1;1<(d|0)&&(d>>=1);if(2>b)break;p=g[m]=GL.Texture.getTemporary(b,d,f);n[0]=1/k.width;n[1]=1/k.height;k.blit(p,l.uniforms(e));k=p}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/k.width,n[1]=1/k.height,e.u_intensity=q,e.u_delta=1,k.blit(b,l.uniforms(e)),this.setOutputData(2,b));gl.enable(gl.BLEND);gl.blendFunc(gl.ONE,gl.ONE);e.u_intensity= +this.getInputOrProperty("persistence");e.u_delta=0.5;for(m-=2;0<=m;m--)p=g[m],g[m]=null,n[0]=1/k.width,n[1]=1/k.height,k.blit(p,l.uniforms(e)),GL.Texture.releaseTemporary(k),k=p;gl.disable(gl.BLEND);this.isOutputConnected(1)&&(g=this._glow_texture,g&&g.width==a.width&&g.height==a.height&&g.type==h&&g.format==a.format||(g=this._glow_texture=new GL.Texture(a.width,a.height,{type:h,format:a.format,filter:gl.LINEAR})),k.blit(g),this.setOutputData(1,g));if(this.isOutputConnected(0)){g=this._final_texture; +g&&g.width==a.width&&g.height==a.height&&g.type==h&&g.format==a.format||(g=this._final_texture=new GL.Texture(a.width,a.height,{type:h,format:a.format,filter:gl.LINEAR}));var u=this.getInputData(1),w=this.getInputOrProperty("dirt_factor");e.u_intensity=q;l=u?r._dirt_final_shader:r._final_shader;l||(l=u?r._dirt_final_shader=new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER,r.final_pixel_shader,{USE_DIRT:""}):r._final_shader=new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER,r.final_pixel_shader));g.drawTo(function(){a.bind(0); +k.bind(1);u&&(l.setUniform("u_dirt_factor",w),l.setUniform("u_dirt_texture",u.bind(2)));l.toViewport(e)});this.setOutputData(0,g)}GL.Texture.releaseTemporary(k)}},r.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}",r.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}", +r.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",r),H.title="Kuwahara Filter",H.desc="Filters a texture giving an artistic oil canvas painting",H.max_radius=10,H._shaders=[],H.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),H.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;H._shaders[b]||(H._shaders[b]=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,H.pixel_shader,{RADIUS:b.toFixed(0)}));var f=H._shaders[b],h=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(h)}); this.setOutputData(0,this._temp_texture)}}},H.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",H),I.title="XDoG Filter",I.desc="Filters a texture giving an artistic ink style",I.max_radius=10,I._shaders=[],I.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}));I._xdog_shader||(I._xdog_shader=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,I.xdog_pixel_shader)); -var d=I._xdog_shader,c=GL.Mesh.getScreenQuad(),f=this.properties.sigma,h=this.properties.k,k=this.properties.p,e=this.properties.epsilon,g=this.properties.phi;a.bind(0);this._temp_texture.drawTo(function(){d.uniforms({src:0,sigma:f,k:h,p:k,epsilon:e,phi:g,cvsWidth:a.width,cvsHeight:a.height}).draw(c)});this.setOutputData(0,this._temp_texture)}},I.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}", +var d=I._xdog_shader,c=GL.Mesh.getScreenQuad(),f=this.properties.sigma,h=this.properties.k,e=this.properties.p,g=this.properties.epsilon,l=this.properties.phi;a.bind(0);this._temp_texture.drawTo(function(){d.uniforms({src:0,sigma:f,k:h,p:e,epsilon:g,phi:l,cvsWidth:a.width,cvsHeight:a.height}).draw(c)});this.setOutputData(0,this._temp_texture)}},I.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",I),G.title="Webcam",G.desc="Webcam texture",G.is_webcam_open=!1,G.prototype.openStream=function(){function a(d){G.is_webcam_open=!1;console.log("Webcam rejected",d);b._webcam_stream=!1;b.boxcolor="red";b.trigger("stream_error")}if(navigator.getUserMedia){this._waiting_confirmation=!0;navigator.mediaDevices.getUserMedia({audio:!1,video:{facingMode:this.properties.facingMode}}).then(this.streamReady.bind(this))["catch"](a);var b=this}},G.prototype.closeStream=function(){if(this._webcam_stream){var a= this._webcam_stream.getTracks();if(a.length)for(var b=0;b=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())},G.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;aMath.abs(d))return c[1];d=(a-c[0])/d;return c[1]*(1-d)+f[1]*d}}return 0}},u.prototype.updateCurve=function(){for(var a=this._values,b=a.length/4,d=this.properties.split_channels,c=0;c=f;){h=0.5*(e+f)|0;c=a[h];if(c==d)break;if(f==e-1)return f;c -a&&(a=1);this.points&&this.points.length==3*a||(this.points=new Float32Array(3*a));this.properties.generate_normals?this.normals&&this.normals.length==this.points.length||(this.normals=new Float32Array(this.points.length)):this.normals=null;var d=this._last_radius||this.properties.radius,c=this.properties.mode,f=this.getInputData(0);this._old_obj_version=f?f._version:null;this.points=m.generatePoints(d,a,c,this.points,this.normals,this.properties.regular,f);this.version++};m.generatePoints=function(a, -d,c,f,h,e,g){var p=3*d;f&&f.length==p||(f=new Float32Array(p));var s=new Float32Array(3),l=new Float32Array([0,1,0]);if(e)if(c==m.RECTANGLE){p=Math.floor(Math.sqrt(d));for(d=0;de||rg&&gp))break}this.geometry.indices=this.indices=new Uint32Array(s)}this.indices&&this.indices.length? -(this.geometry.indices=this.indices,this.setOutputData(0,this.geometry)):this.setOutputData(0,null)}};A.registerNodeType("geometry/connectPoints",v);"undefined"!=typeof GL&&(D.title="to geometry",D.desc="converts a mesh to geometry",D.prototype.onExecute=function(){var a=this.getInputData(0);if(a){if(a!=this.last_mesh){this.last_mesh=a;for(i in a.vertexBuffers)this.geometry[i]=a.vertexBuffers[i].data;a.indexBuffers.triangles&&(this.geometry.indices=a.indexBuffers.triangles.data);this.geometry._id= -c();this.geometry._version=0}this.setOutputData(0,this.geometry);this.geometry&&this.setOutputData(1,this.geometry.vertices)}},A.registerNodeType("geometry/toGeometry",D),B.title="Geo to Mesh",B.prototype.updateMesh=function(a){this.mesh||(this.mesh=new GL.Mesh);for(var d in a)if("_"!=d[0]){var c=a[d],h=GL.Mesh.common_buffers[d];if(h||"indices"==d){var h=h?h.spacing:3,e=this.mesh.vertexBuffers[d];e&&e.data.length==c.length?(e.data.set(c),e.upload(GL.DYNAMIC_DRAW)):e=new GL.Buffer("indices"==d?GL.ELEMENT_ARRAY_BUFFER: -GL.ARRAY_BUFFER,c,h,GL.DYNAMIC_DRAW);this.mesh.addBuffer(d,e)}}if(this.mesh.vertexBuffers.normals&&this.mesh.vertexBuffers.normals.data.length!=this.mesh.vertexBuffers.vertices.data.length){c=new Float32Array([0,1,0]);h=new Float32Array(this.mesh.vertexBuffers.vertices.data.length);for(d=0;d=f;){e=0.5*(g+f)|0;c=a[e];if(c==d)break;if(f==g-1)return f;ca&&(a=1);this.points&&this.points.length==3*a||(this.points=new Float32Array(3*a));this.properties.generate_normals?this.normals&&this.normals.length==this.points.length||(this.normals=new Float32Array(this.points.length)):this.normals=null;var d=this._last_radius||this.properties.radius,c=this.properties.mode,f=this.getInputData(0);this._old_obj_version=f?f._version:null;this.points=m.generatePoints(d,a,c,this.points, +this.normals,this.properties.regular,f);this.version++};m.generatePoints=function(a,d,c,f,e,g,l){var p=3*d;f&&f.length==p||(f=new Float32Array(p));var k=new Float32Array(3),s=new Float32Array([0,1,0]);if(g)if(c==m.RECTANGLE){p=Math.floor(Math.sqrt(d));for(d=0;dg||tl&&lp))break}this.geometry.indices=this.indices=new Uint32Array(k)}this.indices&&this.indices.length?(this.geometry.indices=this.indices,this.setOutputData(0,this.geometry)):this.setOutputData(0,null)}};A.registerNodeType("geometry/connectPoints",v);"undefined"!=typeof GL&&(E.title="to geometry",E.desc="converts a mesh to geometry",E.prototype.onExecute= +function(){var a=this.getInputData(0);if(a){if(a!=this.last_mesh){this.last_mesh=a;for(i in a.vertexBuffers)this.geometry[i]=a.vertexBuffers[i].data;a.indexBuffers.triangles&&(this.geometry.indices=a.indexBuffers.triangles.data);this.geometry._id=c();this.geometry._version=0}this.setOutputData(0,this.geometry);this.geometry&&this.setOutputData(1,this.geometry.vertices)}},A.registerNodeType("geometry/toGeometry",E),B.title="Geo to Mesh",B.prototype.updateMesh=function(a){this.mesh||(this.mesh=new GL.Mesh); +for(var d in a)if("_"!=d[0]){var c=a[d],f=GL.Mesh.common_buffers[d];if(f||"indices"==d){var f=f?f.spacing:3,e=this.mesh.vertexBuffers[d];e&&e.data.length==c.length?(e.data.set(c),e.upload(GL.DYNAMIC_DRAW)):e=new GL.Buffer("indices"==d?GL.ELEMENT_ARRAY_BUFFER:GL.ARRAY_BUFFER,c,f,GL.DYNAMIC_DRAW);this.mesh.addBuffer(d,e)}}if(this.mesh.vertexBuffers.normals&&this.mesh.vertexBuffers.normals.data.length!=this.mesh.vertexBuffers.vertices.data.length){c=new Float32Array([0,1,0]);f=new Float32Array(this.mesh.vertexBuffers.vertices.data.length); +for(d=0;d=c.NOTEON||g<=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,h=Math.floor((e-24)/12+1);l=(e-21)%12;0>l&&(l=12+l);return c.notes[l]+(g?"":h)};c.NoteStringToPitch=function(e){e=e.toUpperCase();var g=e[0],l=4;"#"==e[1]?(g+="#",2this.properties.max_value)return;this.trigger("on_midi",g)}};q.registerNodeType("midi/filter",e);C.title="MIDIEvent";C.desc="Create a MIDI Event";C.color="#243";C.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))};C.prototype.onExecute=function(){var e=this.properties;if(this.inputs)for(var g=0;gc;++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}}};B.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))};B.prototype.onExecute=function(){var c=this.getInputData(1);null!=c&&c!=this._current_scale&& -this.processScale(c)};q.registerNodeType("midi/quantize",B);g.title="MIDI Play";g.desc="Plays a MIDI note";g.color="#243";g.prototype.onAction=function(e,g){if(g&&g.constructor===c){if(this.instrument&&g.data[0]==c.NOTEON){var l=g.note;if(!l||"undefined"==l||l.constructor!==String)return;this.instrument.play(l,g.octave,this.properties.duration,this.properties.volume)}this.trigger("note",g)}};g.prototype.onExecute=function(){var c=this.getInputData(1);null!=c&&(this.properties.volume=c);c=this.getInputData(2); -null!=c&&(this.properties.duration=c)};q.registerNodeType("midi/play",g);r.title="MIDI Keys";r.desc="Keyboard to play notes";r.color="#243";r.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}];r.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),h=this.size[1];c.globalAlpha=1;for(var a=0;2>a;a++)for(var b=0;bd+k||c[1]>b))return a}}return-1};r.prototype.onAction=function(e,g){if("reset"==e)for(var l=0;lg[1])){var l=this.getKeyIndex(g);this.keys[l]=!0;this._last_key= -l;var l=12*(this.properties.start_octave-1)+29+l,h=new c;h.setup([c.NOTEON,l,100]);this.trigger("note",h);return!0}};r.prototype.onMouseMove=function(e,g){if(!(0>g[1]||-1==this._last_key)){this.setDirtyCanvas(!0);var l=this.getKeyIndex(g);if(this._last_key==l)return!0;this.keys[this._last_key]=!1;var h=12*(this.properties.start_octave-1)+29+this._last_key,a=new c;a.setup([c.NOTEOFF,h,100]);this.trigger("note",a);this.keys[l]=!0;h=12*(this.properties.start_octave-1)+29+l;a=new c;a.setup([c.NOTEON, -h,100]);this.trigger("note",a);this._last_key=l;return!0}};r.prototype.onMouseUp=function(e,g){if(!(0>g[1])){var l=this.getKeyIndex(g);this.keys[l]=!1;this._last_key=-1;var l=12*(this.properties.start_octave-1)+29+l,h=new c;h.setup([c.NOTEOFF,l,100]);this.trigger("note",h);return!0}};q.registerNodeType("midi/keys",r)})(this); -(function(y){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=t.getAudioContext().createGain();this.audionode.graphnode=this;this.audionode.gain.value=this.properties.gain;this.properties.src&&this.loadSound(this.properties.src)}function m(){this.properties={gain:0.5};this._audionodes=[];this._media_stream= -null;this.addOutput("out","audio");this.addInput("gain","number");this.audionode=t.getAudioContext().createGain();this.audionode.graphnode=this;this.audionode.gain.value=this.properties.gain}function n(){this.properties={fftSize:2048,minDecibels:-100,maxDecibels:-10,smoothingTimeConstant:0.5};this.audionode=t.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 l(){this.properties={gain:1};this.audionode=t.getAudioContext().createGain();this.addInput("in","audio");this.addInput("gain","number");this.addOutput("out","audio")}function x(){this.properties={impulse_src:"",normalize:!0};this.audionode=t.getAudioContext().createConvolver(); -this.addInput("in","audio");this.addOutput("out","audio")}function e(){this.properties={threshold:-50,knee:40,ratio:12,reduction:-20,attack:0,release:0.25};this.audionode=t.getAudioContext().createDynamicsCompressor();this.addInput("in","audio");this.addOutput("out","audio")}function C(){this.properties={};this.audionode=t.getAudioContext().createWaveShaper();this.addInput("in","audio");this.addInput("shape","waveshape");this.addOutput("out","audio")}function z(){this.properties={gain1:0.5,gain2:0.5}; -this.audionode=t.getAudioContext().createGain();this.audionode1=t.getAudioContext().createGain();this.audionode1.gain.value=this.properties.gain1;this.audionode2=t.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 v(){this.properties= -{A:0.1,D:0.1,S:0.1,R:0.1};this.audionode=t.getAudioContext().createGain();this.audionode.gain.value=0;this.addInput("in","audio");this.addInput("gate","bool");this.addOutput("out","audio");this.gate=!1}function D(){this.properties={delayTime:0.5};this.audionode=t.getAudioContext().createDelay(10);this.audionode.delayTime.value=this.properties.delayTime;this.addInput("in","audio");this.addInput("time","number");this.addOutput("out","audio")}function B(){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=t.getAudioContext().createBiquadFilter();this.addInput("in","audio");this.addOutput("out","audio")}function g(){this.properties={frequency:440,detune:0,type:"sine"};this.addProperty("type","sine","enum",{values:["sine","square","sawtooth","triangle","custom"]});this.audionode=t.getAudioContext().createOscillator();this.addOutput("out","audio")}function r(){this.properties= -{continuous:!0,mark:-1};this.addInput("data","array");this.addInput("mark","number");this.size=[300,200];this._last_buffer=null}function q(){this.properties={band:440,amplitude:1};this.addInput("freqs","array");this.addOutput("signal","number")}function A(){if(!A.default_code){var c=A.default_function.toString(),a=c.indexOf("{")+1,b=c.lastIndexOf("}");A.default_code=c.substr(a,b-a)}this.properties={code:A.default_code};c=t.getAudioContext();c.createScriptProcessor?this.audionode=c.createScriptProcessor(4096, -1,1):(console.warn("ScriptProcessorNode deprecated"),this.audionode=c.createGain());this.processCode();A._bypass_function||(A._bypass_function=this.audionode.onaudioprocess);this.addInput("in","audio");this.addOutput("out","audio")}function s(){this.audionode=t.getAudioContext().destination;this.addInput("in","audio")}var p=y.LiteGraph,t={};y.LGAudio=t;t.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};t.connect=function(c,a){try{c.connect(a)}catch(b){console.warn("LGraphAudio:",b)}};t.disconnect=function(c,a){try{c.disconnect(a)}catch(b){console.warn("LGraphAudio:",b)}};t.changeAllAudiosConnections=function(c,a){if(c.inputs)for(var b= -0;b=c.NOTEON||g<=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 k,l=Math.floor((e-24)/12+1);k=(e-21)%12;0>k&&(k=12+k);return c.notes[k]+(g?"":l)};c.NoteStringToPitch= +function(e){e=e.toUpperCase();var g=e[0],k=4;"#"==e[1]?(g+="#",2this.properties.max_value)return;this.trigger("on_midi", +g)}};t.registerNodeType("midi/filter",g);C.title="MIDIEvent";C.desc="Create a MIDI Event";C.color="#243";C.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))};C.prototype.onExecute=function(){var e=this.properties;if(this.inputs)for(var g=0;gc;++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}}};B.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))};B.prototype.onExecute=function(){var c=this.getInputData(1);null!=c&&c!=this._current_scale&&this.processScale(c)};t.registerNodeType("midi/quantize",B);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){var k=g.note;if(!k||"undefined"==k||k.constructor!==String)return;this.instrument.play(k,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)};t.registerNodeType("midi/play",e);q.title="MIDI Keys";q.desc="Keyboard to play notes";q.color="#243";q.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}];q.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),l=this.size[1];c.globalAlpha=1;for(var a=0;2>a;a++)for(var b=0;bd+h||c[1]>b))return a}}return-1};q.prototype.onAction=function(e,g){if("reset"==e)for(var k=0;kg[1])){var k=this.getKeyIndex(g);this.keys[k]=!0;this._last_key=k;var k=12*(this.properties.start_octave-1)+29+k,l=new c;l.setup([c.NOTEON,k,100]);this.trigger("note",l);return!0}};q.prototype.onMouseMove=function(e,g){if(!(0>g[1]||-1==this._last_key)){this.setDirtyCanvas(!0);var k=this.getKeyIndex(g);if(this._last_key==k)return!0;this.keys[this._last_key]=!1;var l=12*(this.properties.start_octave-1)+29+this._last_key,a=new c;a.setup([c.NOTEOFF,l,100]); +this.trigger("note",a);this.keys[k]=!0;l=12*(this.properties.start_octave-1)+29+k;a=new c;a.setup([c.NOTEON,l,100]);this.trigger("note",a);this._last_key=k;return!0}};q.prototype.onMouseUp=function(e,g){if(!(0>g[1])){var k=this.getKeyIndex(g);this.keys[k]=!1;this._last_key=-1;var k=12*(this.properties.start_octave-1)+29+k,l=new c;l.setup([c.NOTEOFF,k,100]);this.trigger("note",l);return!0}};t.registerNodeType("midi/keys",q)})(this); +(function(y){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=w.getAudioContext().createGain();this.audionode.graphnode=this;this.audionode.gain.value=this.properties.gain;this.properties.src&&this.loadSound(this.properties.src)}function m(){this.properties={gain:0.5};this._audionodes=[];this._media_stream= +null;this.addOutput("out","audio");this.addInput("gain","number");this.audionode=w.getAudioContext().createGain();this.audionode.graphnode=this;this.audionode.gain.value=this.properties.gain}function n(){this.properties={fftSize:2048,minDecibels:-100,maxDecibels:-10,smoothingTimeConstant:0.5};this.audionode=w.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 k(){this.properties={gain:1};this.audionode=w.getAudioContext().createGain();this.addInput("in","audio");this.addInput("gain","number");this.addOutput("out","audio")}function x(){this.properties={impulse_src:"",normalize:!0};this.audionode=w.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=w.getAudioContext().createDynamicsCompressor();this.addInput("in","audio");this.addOutput("out","audio")}function C(){this.properties={};this.audionode=w.getAudioContext().createWaveShaper();this.addInput("in","audio");this.addInput("shape","waveshape");this.addOutput("out","audio")}function z(){this.properties={gain1:0.5,gain2:0.5}; +this.audionode=w.getAudioContext().createGain();this.audionode1=w.getAudioContext().createGain();this.audionode1.gain.value=this.properties.gain1;this.audionode2=w.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 v(){this.properties= +{A:0.1,D:0.1,S:0.1,R:0.1};this.audionode=w.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=w.getAudioContext().createDelay(10);this.audionode.delayTime.value=this.properties.delayTime;this.addInput("in","audio");this.addInput("time","number");this.addOutput("out","audio")}function B(){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=w.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=w.getAudioContext().createOscillator();this.addOutput("out","audio")}function q(){this.properties= +{continuous:!0,mark:-1};this.addInput("data","array");this.addInput("mark","number");this.size=[300,200];this._last_buffer=null}function t(){this.properties={band:440,amplitude:1};this.addInput("freqs","array");this.addOutput("signal","number")}function A(){if(!A.default_code){var c=A.default_function.toString(),a=c.indexOf("{")+1,b=c.lastIndexOf("}");A.default_code=c.substr(a,b-a)}this.properties={code:A.default_code};c=w.getAudioContext();c.createScriptProcessor?this.audionode=c.createScriptProcessor(4096, +1,1):(console.warn("ScriptProcessorNode deprecated"),this.audionode=c.createGain());this.processCode();A._bypass_function||(A._bypass_function=this.audionode.onaudioprocess);this.addInput("in","audio");this.addOutput("out","audio")}function p(){this.audionode=w.getAudioContext().destination;this.addInput("in","audio")}var s=y.LiteGraph,w={};y.LGAudio=w;w.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};w.connect=function(c,a){try{c.connect(a)}catch(b){console.warn("LGraphAudio:",b)}};w.disconnect=function(c,a){try{c.disconnect(a)}catch(b){console.warn("LGraphAudio:",b)}};w.changeAllAudiosConnections=function(c,a){if(c.inputs)for(var b= +0;b=this.size[0]&&(e=this.size[0]-1),c.strokeStyle="red",c.beginPath(),c.moveTo(e,d),c.lineTo(e,0),c.stroke())}};r.title="Visualization";r.desc="Audio Visualization";p.registerNodeType("audio/visualization",r);q.prototype.onExecute=function(){if(this._freqs=this.getInputData(0)){var c=this.properties.band,a=this.getInputData(1);void 0!==a&&(c=a);a=t.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)}};q.prototype.onGetInputs=function(){return[["band","number"]]};q.title="Signal";q.desc="extract the signal of some frequency";p.registerNodeType("audio/signal",q);A.prototype.onAdded=function(c){c.status==LGraph.STATUS_RUNNING&&(this.audionode.onaudioprocess=this._callback)};A["@code"]={widget:"code"};A.prototype.onStart=function(){this.audionode.onaudioprocess=this._callback};A.prototype.onStop= +b&&(this.audionode[a.name].value=b)}}};n.prototype.onGetInputs=function(){return[["minDecibels","number"],["maxDecibels","number"],["smoothingTimeConstant","number"]]};n.prototype.onGetOutputs=function(){return[["freqs","array"],["samples","array"]]};n.title="Analyser";n.desc="Audio Analyser";s.registerNodeType("audio/analyser",n);k.prototype.onExecute=function(){if(this.inputs&&this.inputs.length)for(var c=1;c=this.size[0]&&(e=this.size[0]-1),c.strokeStyle="red",c.beginPath(),c.moveTo(e,d),c.lineTo(e,0),c.stroke())}};q.title="Visualization";q.desc="Audio Visualization";s.registerNodeType("audio/visualization",q);t.prototype.onExecute=function(){if(this._freqs=this.getInputData(0)){var c=this.properties.band,a=this.getInputData(1);void 0!==a&&(c=a);a=w.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)}};t.prototype.onGetInputs=function(){return[["band","number"]]};t.title="Signal";t.desc="extract the signal of some frequency";s.registerNodeType("audio/signal",t);A.prototype.onAdded=function(c){c.status==LGraph.STATUS_RUNNING&&(this.audionode.onaudioprocess=this._callback)};A["@code"]={widget:"code",type:"code"};A.prototype.onStart=function(){this.audionode.onaudioprocess=this._callback};A.prototype.onStop= function(){this.audionode.onaudioprocess=A._bypass_function};A.prototype.onPause=function(){this.audionode.onaudioprocess=A._bypass_function};A.prototype.onUnpause=function(){this.audionode.onaudioprocess=this._callback};A.prototype.onExecute=function(){};A.prototype.onRemoved=function(){this.audionode.onaudioprocess=A._bypass_function};A.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(c){console.error("Error in onaudioprocess code",c),this._callback=A._bypass_function,this.audionode.onaudioprocess=this._callback}};A.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))};A.default_function=function(){this.onaudioprocess=function(c){var a=c.inputBuffer;c=c.outputBuffer;for(var b=0;b + diff --git a/demo/js/code.js b/demo/js/code.js index c1b7972bb..cf7fd00bf 100644 --- a/demo/js/code.js +++ b/demo/js/code.js @@ -1,3 +1,4 @@ +var webgl_canvas = null; LiteGraph.node_images_path = "../nodes_data/"; var editor = new LiteGraph.Editor("main"); @@ -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,80 @@ 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/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/demo/js/demos.js index 921704f66..ea1e06a3c 100644 --- a/demo/js/demos.js +++ b/demo/js/demos.js @@ -99,7 +99,6 @@ TestWidgetsNode.title = "Widgets"; LiteGraph.registerNodeType("features/widgets", TestWidgetsNode ); - //Show value inside the debug console function TestSpecialNode() { diff --git a/demo/js/libs/gl-matrix-min.js b/demo/js/libs/gl-matrix-min.js new file mode 100644 index 000000000..747b3a77e --- /dev/null +++ b/demo/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/demo/js/libs/litegl.js b/demo/js/libs/litegl.js new file mode 100644 index 000000000..f9ae7f822 --- /dev/null +++ b/demo/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/src/litegraph-editor.js b/src/litegraph-editor.js index abf77fcbf..4b6042860 100755 --- a/src/litegraph-editor.js +++ b/src/litegraph-editor.js @@ -3,12 +3,9 @@ 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; @@ -16,6 +13,7 @@ function Editor(container_id, options) { 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,9 @@ function Editor(container_id, options) { graphcanvas.draw(true); }; + graphcanvas.onDropItem = this.onDropItem.bind(this); + graphcanvas.onShowNodePanel = this.onShowNodePanel.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 +105,159 @@ 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) { + options = options || {}; + + var ref_window = options.window || window; var root = document.createElement("div"); root.className = "dialog"; - root.innerHTML = - "
" + - title + - "
"; + root.innerHTML = "
"; root.header = root.querySelector(".dialog-header"); + if(options.closable) + { + var close = document.createElement("span"); + close.innerHTML = "✕"; + close.classList.add("close"); + close.addEventListener("click",function(){ + root.close(); + }); + root.header.appendChild(close); + } + root.title_element = root.querySelector(".dialog-title"); + root.title_element.innerText = title; root.content = root.querySelector(".dialog-content"); root.footer = root.querySelector(".dialog-footer"); + root.close = function() + { + this.parentNode.removeChild(this); + } + + root.addButton = function( name, callback, options ) + { + var elem = document.createElement("button"); + elem.innerText = name; + elem.options = options; + elem.addEventListener("click",callback); + root.footer.appendChild(elem); + return elem; + } + + root.addWidget = function( type, name, value, options, callback ) + { + options = options || {}; + var str_value = String(value); + if(type == "number") + str_value = value.toFixed(3); + + var elem = document.createElement("div"); + elem.className = "property"; + elem.innerHTML = ""; + elem.querySelector(".property_name").innerText = name; + var value_element = elem.querySelector(".property_value"); + value_element.innerText = str_value; + elem.dataset["property"] = name; + elem.dataset["type"] = options.type || type; + elem.options = options; + elem.value = value; + + //if( type == "code" ) + // elem.addEventListener("click", function(){ inner_showCodePad( node, this.dataset["property"] ); }); + if (type == "boolean") + { + elem.classList.add("boolean"); + if(value) + elem.classList.add("bool-on"); + elem.addEventListener("click", function(){ + //var v = node.properties[this.dataset["property"]]; + //node.setProperty(this.dataset["property"],!v); this.innerText = v ? "true" : "false"; + var propname = this.dataset["property"]; + this.value = !this.value; + this.classList.toggle("bool-on"); + this.querySelector(".property_value").innerText = this.value ? "true" : "false"; + innerChange(propname, this.value ); + }); + } + else if (type == "string" || type == "number") + { + value_element.setAttribute("contenteditable",true); + value_element.addEventListener("keydown", function(e){ + if(e.code == "Enter") + { + e.preventDefault(); + this.blur(); + } + }); + value_element.addEventListener("blur", function(){ + var v = this.innerText; + var propname = this.parentNode.dataset["property"]; + var proptype = this.parentNode.dataset["type"]; + if( proptype == "number") + v = Number(v); + innerChange(propname, v); + }); + } + else if (type == "enum") + value_element.addEventListener("click", function(event){ + var values = options.values || []; + var propname = this.parentNode.dataset["property"]; + var elem_that = this; + var menu = new LiteGraph.ContextMenu(values,{ + event: event, + className: "dark", + callback: inner_clicked + }, + ref_window); + function inner_clicked(v, option, event) { + //node.setProperty(propname,v); + //graphcanvas.dirty_canvas = true; + elem_that.innerText = v; + innerChange(propname,v); + return false; + } + }); + + root.content.appendChild(elem); + + function innerChange(name, value) + { + console.log("change",name,value); + //that.dirty_canvas = true; + if(options.callback) + options.callback(name,value); + if(callback) + callback(name,value); + } + + return elem; + } 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.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.createPanel("Load session",{closable:true}); + //TO DO this.root.appendChild(panel); }; @@ -192,6 +296,177 @@ 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); + } + } +} + +//shows the left side panel with the node info +Editor.prototype.onShowNodePanel = function(node) +{ + var panel = document.querySelector("#node-panel"); + if(panel) + panel.close(); + var ref_window = this.graphcanvas.getCanvasWindow(); + panel = this.createPanel(node.title || "",{closable: true, window: ref_window }); + panel.id = "node-panel"; + panel.classList.add("settings"); + var that = this; + var graphcanvas = this.graphcanvas; + + function inner_refresh() + { + panel.content.innerHTML = ""; //clear + var elem = document.createElement("div"); + elem.innerHTML = ""+node.type+""+(node.constructor.desc || "")+""; + panel.content.appendChild(elem); + + for(var i in node.properties) + { + var value = node.properties[i]; + var info = node.getPropertyInfo(i); + var type = info.type || "string"; + panel.addWidget( info.widget || info.type, i, value, info, function(name,value){ + node.setProperty(name,value); + graphcanvas.dirty_canvas = true; + }); + } + + panel.addButton("Delete",function(){ + node.graph.remove(node); + panel.close(); + }).classList.add("delete"); + + /* + for(var i in node.properties) + { + var value = node.properties[i]; + var type = "string"; + var info = node.getPropertyInfo(i); + var type = info.type; + + var str_value = String(value); + if(type == "number") + str_value = value.toFixed(3); + + var elem = document.createElement("div"); + elem.className = "property"; + elem.innerHTML = ""; + elem.querySelector(".property_name").innerText = i; + var value_element = elem.querySelector(".property_value"); + value_element.innerText = str_value; + elem.dataset["property"] = i; + elem.dataset["type"] = type; + elem.datainfo = info; + + if( type == "code" ) + elem.addEventListener("click", function(){ inner_showCodePad( node, this.dataset["property"] ); }); + else if (type == "boolean") + elem.addEventListener("click", function(){ + var v = node.properties[this.dataset["property"]]; + node.setProperty(this.dataset["property"],!v); this.innerText = v ? "true" : "false"; + }); + else if (type == "string" || type == "number") + { + value_element.setAttribute("contenteditable",true); + value_element.addEventListener("change", function(){ + var v = this.innerText; + var propname = this.parentNode.dataset["property"]; + if( propname == "number") + v = Number(v); + node.setProperty(propname,v); + that.dirty_canvas = true; + }); + } + else if (type == "enum") + value_element.addEventListener("click", function(event){ + var values = this.parentNode.datainfo.values || []; + var propname = this.parentNode.dataset["property"]; + var elem_that = this; + var menu = new LiteGraph.ContextMenu(values,{ + event: event, + className: "dark", + callback: inner_clicked + }, + ref_window); + function inner_clicked(v, option, event) { + node.setProperty(propname,v); + elem_that.innerText = v; + graphcanvas.dirty_canvas = true; + return false; + } + }); + else //generic + elem.addEventListener("click", function(){ + that.graphcanvas.showEditPropertyValue( node, this.dataset["property"], {onclose: inner_refresh} ); + }); + panel.content.appendChild(elem); + } + */ + } + + function inner_showCodePad( node, propname ) + { + panel.style.top = "calc( 50% - 250px)"; + panel.style.left = "calc( 50% - 400px)"; + panel.style.width = "800px"; + panel.style.height = "500px"; + + if(window.CodeFlask) //disabled for now + { + panel.content.innerHTML = "
"; + var flask = new CodeFlask( "div.code", { language: 'js' }); + flask.updateCode(node.properties[propname]); + flask.onUpdate( function(code) { + node.setProperty(propname, code); + }); + } + else + { + panel.content.innerHTML = ""; + 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.content.appendChild( panel ); +} + Editor.prototype.goFullscreen = function() { if (this.root.requestFullscreen) { this.root.requestFullscreen(Element.ALLOW_KEYBOARD_INPUT); diff --git a/src/litegraph.js b/src/litegraph.js index e61fc95fb..04ca903cd 100755 --- a/src/litegraph.js +++ b/src/litegraph.js @@ -132,8 +132,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) { @@ -161,11 +165,25 @@ }, enumerable: 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) { @@ -177,26 +195,22 @@ 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 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; - } - } }, + /** + * 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. @@ -681,24 +695,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); + if(that.onBeforeStep) + that.onBeforeStep(); that.runStep(1, !this.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 + if(that.onBeforeStep) + that.onBeforeStep(); that.runStep(1, !this.catch_errors); + if(that.onAfterStep) + that.onAfterStep(); }, interval); } }; @@ -1893,7 +1912,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]; } @@ -2203,6 +2222,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 +2286,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 +2390,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 ************************* @@ -3130,13 +3166,51 @@ }; /** - * 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]; + + //litescene mode using the constructor + if (this.onGetPropertyInfo) { + info = this.onGetPropertyInfo(property); + } + + if (!info) + info = {}; + if(!info.type) + info.type = typeof this.properties[property]; + + 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 */ @@ -5452,16 +5526,8 @@ LGraphNode.prototype.executeAction = function(action) 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); @@ -5470,6 +5536,12 @@ LGraphNode.prototype.executeAction = function(action) this.last_mouse_dragging = false; if (e.which == 1) { + + if( this.node_widget ) + { + this.processNodeWidgets( this.node_widget[0], this.canvas_mouse, e ); + } + //left button this.node_widget = null; @@ -8280,7 +8352,7 @@ 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; if ( w == active_widget || (x > 6 && x < width - 12 && y > w.last_y && y < w.last_y + LiteGraph.NODE_WIDGET_HEIGHT) ) { //inside widget @@ -8314,18 +8386,11 @@ LGraphNode.prototype.executeAction = function(action) 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 += 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 - ) { + if ( w.options.max != null && w.value > w.options.max ) { w.value = w.options.max; } } else if (event.type == "mousedown") { @@ -8337,38 +8402,33 @@ LGraphNode.prototype.executeAction = function(action) 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 - ) { + 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 - ) { + 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; + } else if (delta) { //used for combos + var values_list = values.constructor === Array ? values : Object.keys(values); + var index = values_list.indexOf(w.value) + delta; if (index >= values.length) { index = 0; } if (index < 0) { - index = values.length - 1; + index = values_list.length - 1; } - w.value = values[index]; - } else { - var menu = new LiteGraph.ContextMenu( - values, - { + if( values.constructor === Array ) + w.value = values[index]; + else + w.value = values[ values_list[index] ]; + } else { //combo + var menu = new LiteGraph.ContextMenu(values,{ scale: Math.max(1, this.ds.scale), event: event, className: "dark", callback: inner_clicked.bind(w) }, - ref_window - ); + ref_window); function inner_clicked(v, option, event) { this.value = v; inner_value_change(this, v); @@ -8376,7 +8436,18 @@ LGraphNode.prototype.executeAction = function(action) return false; } } - } //mousedown + } //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), + event); + } + } if( old_value != w.value ) setTimeout( @@ -8398,15 +8469,11 @@ LGraphNode.prototype.executeAction = function(action) case "string": case "text": if (event.type == "mousedown") { - this.prompt( - "Value", - w.value, - function(v) { + this.prompt("Value",w.value,function(v) { this.value = v; inner_value_change(this, v); }.bind(w), - event - ); + event); } break; default: @@ -9421,11 +9488,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,28 +9496,8 @@ 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 = ""; @@ -9548,9 +9591,13 @@ LGraphNode.prototype.executeAction = function(action) if (node.onPropertyChanged) { node.onPropertyChanged(property, value); } + if(options.onclose) + options.onclose(); dialog.close(); node.setDirtyCanvas(true, true); } + + return dialog; }; LGraphCanvas.prototype.createDialog = function(html, options) { diff --git a/src/nodes/.gltextures.js.swp b/src/nodes/.gltextures.js.swp deleted file mode 100644 index 5e286edeb1d9e899a3603182e7bbc1dbb89579f8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 40960 zcmeI53z%e8b?;ju#0~-hjPYU&XBwl`Gd)$^Jum2C2=nN9gn6aAhXKc?I9+|ZyJo8D zl=bMIVHl0^({S~Mh=|7A+=Q#B*CbwV)C=|R$r^lsZz0!pKFv$xoXWn z=oK3ET&dXbW@~PBj#!0U#h>=-x!F>~JKSj2yn6OvebMZ?1YHSqC154cY|N~@@Qi({ zhKB~_)dhXaoO93FXG6PxyAtS1peuo{1iBLFN}wx&t^~Rg=t|)K0|_*$13hn`oIhvE znm2#vEa65;PmgnvB|{#+vb zYbLyC(w{y>KiKsDIuZYYMEF-tc-6#TV+3O3et-t@A2!F(cA2jJdkqCd-gcnTs*lF?fzi7g3{eLVG z{+A})*6+)IJ|6!e6FzIwe^Vm-!9@ITC&IsA!b_IF7sb>6yb1SA_#KJx2TZt??*+Z_ z`1hM|E1x$f!vC)ccTM`=O@!ZP!mWJv{z5$c&zW%CYU=axM7XK$9-IDIiT-D*t4I0i zNa1yfa8n&Umzngxln6KC>?xb@oiEl8wtaur@b`KXetRPPE)#y#gkN|@JpN})xaI%W zM7R-GkFB4VoEeXAs;kHLziSfVpEl_iP5w_N!tXHQHvNgS;^}|NgxmVLEfM}n6J9at zpN}t8K2|=TFyXd+|864ub`x&-dpZ$r)J>pguR1%PzW&zzzbk>R1iBLFN}wx&t^~Rg z=t`g~fuF1d-07a4L(GM5l=(1oR_h0l`SBl__wSIf?x!n(t^~Rg=t`g~fvyC)66i{x zD}k;Ax)SJ0peuo{1Wu*|j`e36bH#f9`ZIbQKF6G#-V9%2)4Mb6%@ix%v<{Nso$Qm} zCi~>GHE*_9Z+Nxc?3JH5QuQ)@X19E<(46whZnbZ~*{Mi3>+h0-*#CWSf4eMnVgK9p z<0rBCKMOts-VQ3D1h#?GK`(fmHMoz01@KzX51wJ&?_1!j;IF`+f)9Z=fXhJ^ybyee z^~L+aec%RgF8Bs(mVX2O9L$5W!Dm<}Jp@*Pv%neP?^#UzFn9}a!3a1TJkBEGm%(Ge z1+N09f*-K>`3(3&FbURy^S~1z zwzq<}f%V`b;DDEabHJBzq+Aag;C%23@Iw{>e*nG;ZUt`y9#{`v03N3fq#kawi;f*z zl~jK8Q*i5^sox2GN+LTpx^H}RBy1~D@~N8V9x_cV`d*%CH~9uF$l2h`ma^M-Y}=mS zJ2tv`$M}vtyYpN3jP2SukrFe70g3v&^WV37e8;xkqg(QuuACTMTE08?>`!D{Unn*T zb55pA=jeBip3xJR%Dh{Pmb%g`mB=Yhqa-s|^B0^x-Hoi6XT8RRTa*8_+#T<5RnD+D zu2b`7yqZ@jc#^uDT`pg7}Xmie{fY_ZZGX17TC8=RSDrO+t)m5h!$;4~Bi15Qvr zTc^2Pv+hkh4d3CoO|{v8lR3|Es~ohc7G1vj%?7V6f6+o8OYmCrC_zOiTV$h(L4fnB zS2-28?AgBu1}B|m8=St8zJ6q(qodohCEuMk(#iU zIZ`W|-Acu8IFhz4&OjL7@n`JYx+8QmezP*2S|354R7qGVl9TWiguTFo^_05*s zOteI;)oB_^q#~6R;tjK-{H>%cS0elkP9|VZi;Gv=c4m#Fvokfnyl-r0rdcZuIDS80p>T9i6#7b$MMAZhJB zTB4X3b2*)|E=1VC7Uocpj$0}Pg%fRMvUXg;>J7ifyJ!)gNBNjQxy@2zB+;(7jBeey zZ|6k3+G`%VGfAli)qyzSTyb_TNhf1pf~@2(B&isw5<~^RdL&7;dC%S}6J&i=S2c*5 z-<+N6OGencaeO>Kv3+dMz8Dkn`rEZ<%V_@cjXU>^j#F(k`LIxNLL(fAhTAp#Hpt2(7hZA25DI@0jkCMOl3ir;}t8CUCcViNVMUae82$GN(>>rvVk zs-2jll}2Vm!u+(a#wy0^(h+Ct6=Bp|Zl>hUk~{|t0h$(Y-r2ZXR_ew(l5sCiXZxc2A6M8{0TB>g?Y!vE8|H&%QBd>)6I! zqx<)aT^22AQl&9aFl3B4M`=ydD6J8vuQ2Bo4o&%o`<%o`C0aN}X?TY)(gEXbO{ZGV z7b`P9GNB`>cHYv|Il^2Zyl+Blx4yQrZWJ3OsgEdSnCNWDpK?pt7!MG~zLRtqAyLH1 zNWSTewleRjTyDQ#J5<-YPo^>q#+d%n)3t)!?@f_|Iumc} zS!O7~tOw3)E_SL#oo8(EGdfTeTkQXrV`qNC*#EAvIsXUtz3dCffM>DmzXm=FUI$i# z?_t9~3hn}Tg6lyETnc^}Jb*p_-$4yr3SJ4$0AInTzX$vt_$}~yFbXaL>p(B~TWtKh z!F$0QK^dG0ejc0(9>LZZ`~L<|0lUB;I19W8Jb}&s0q_gpd)WEk29E-<|8D{}f&y3% z&Iiw7?>`Lg05^f_z%2L;&=15P@MCQLuY>;v{t^5B5%2)G4ZIy30n5O-;9hL~JHe;H zF>n+d0qemz;7sr>?EO2ye*xElw}4&XJJiFsfz-$U1RnnhP$9*WId= zUAF8V&exbIHtRHop+Th&I!3U{Nyiu4)o4Cvwm2{IO}D~qtbvEASaAGe$tkD~;#4ut z8iY$P=$JvE)GVit&PpN@6(!Z%o2Cj#-mBD$4JJiH1CB1!h_goN0uv*m0d$()bl*VG zQAGoUggazX644ZKvZIjNZW7n1mTR3shM7WdF6S6kZi?b8U+PDSObS7d?cltlvn~Xl zJColwnY&22q-4kXt9=WtxD?xuQFYnvEN9V>^3;%;^B;<8&LlfDVE7}vDQe*X@HDW(2QGPCJ);- z-3y8{=tl=1ResBku?hQn-Ycxu8ElaGRJG)7^2s=0-Nv8yi_=b-<|hi=L_TH{F=k91 zCI_>_YiyiemALeY80%8NkoD#~(($v{nj)sf39mV_G`hnJOQXAHA(WBgS7sHe61|d| zs=*q}Eq4eU&a6bdUa8?KIm<(T_VD2&M{J38fdes06sI{KO3VnXSt-u=wX&mT)$tm$ zDJ4mQ*3*tav6T$e42ZBzVJtzs&DqR!1E>veu{@dT2x6tSp|sf1I<~RX=n@$)upSZh zr}#E)P)C=c+J5>%>qLx|s+MD%E~>k|ZM3*7u6(zYuNIXk=GWfo3PX5l{TBahBk1vxD7XhyHe{7QxQ=EN218* zSlUYos4Fd#ZTOdC_QO>pW27G}Hwsnslfsk@ZkLG-k4^}(Q^m@3W;o(tu~gB&eP*g) ziq@AfBBP|HBz3Q`PY+$0xK-Ji)@G#VBuuX&Gn97468kP%BxOHEv$4;yYSslGYL_S_ z)f9ti6tRlVPS@Op2|qOB6a%fLejYWplp(4$LtAe7OY6qgJoFjh0qp+|fOmm+f+F}lyx$A%2JZ$p0`UzLz%Y=qy$n1_Iqw610{#F@ zfeqjl;7R-d4}mX$zW}mFpbz{I+y4>pzrYlDHFzobE`ESJ!A;;OD1uqA9$W;T!4L2> zcocjHxF8Q!fTywlp8|gZUJot-7lR@2G(Lbk!S8`da2|LWco09p=fS<;(-EIQv>m2B zYdeU^WHK;)WI-LZ>l-JKQZmoQllW=GV6i*#S)4EBUsS_yB5M5W@g&4Jeq4;ibFvF{ zC!=P~n%`ncod^TTI46(oj-pOx+m_f8wk2rWO>E5YZ>KauD*MF3X5!N}e#iPrB+`+# zo?S9^oUj)EdR(>mJfg~6w}VEKl-mm}lJAm;mX?&sF_O4?Y@0*d_LHV&bGiDVV%3={ zVJF~oh%0TGep-+21_z_+Xgoa0lKI}2v-af;cupqSJ}phTSh%8UOB7_Qm2EQfQ{_kvs!t7n7|u4Rxc~-7kccru=tDXek)BD%ym1} zR9;in5117NM^;6Z0FN(Li#g(qcQAkA#!ZLDGr$_?*v2h8_KlAi2C><<;<-Gzmf%! zNXjf(>IEXxbetS6efvr^ER|juUv>;G;3L2-I$L7(6py!EJyl6d0|+t?*G&qn*VkOb zuSznP6aDGb{j%36(qu3fy=m67SiaI_DfoC{)b2?w4M{oZ2q{7d!b}TM-Lp4PFO)i= zxr~>_j9k6FM@2N0G^V{y?%85gEO+@`TTd)diQW7qo)0ZJRcR z3;Vp-xnuX}#<6I#77gdHjZXgR^kqR^Qd4aw2-3>NhM8|Ro@8u;)U#>+A`_K?=>Rc( zYAV^*FN^VYw$ZT|&yu{U$7M<~#ky9_oBc|K=A?S2$-qVq=&`EgS!XQ!G*t>HFtr+~ z-Fm9Nq^WeOU;1jpm$GF2%IIMa*QD$du`8CcD!E7kR__t(fT%pN|G$Yny8;_n?ElNm zV)NtJ^>=~yfj5CEup6ucE5OUZIpANg^B)3#25tm-Fa}1!li2q+fH#3_!Cr6y_y)H9 zL*SF(R`93b7H}Ad{eL=m7F+*u@Kx|IcmTW?$hm+u;FUmp0;ho=;RARE+zxz>ko}GvHxxD|k1kfrFp`CcrQdx&0#e z8oq-2!S919a5b0!zXIf}K`;0ga{dDN9k2~pKf>qn7knK2HuxI3K(MoC(eVi+_51&2nIUmKQ_E zP6d~Ld6*T*@^G+J$@d(-IqS549x-D)EG5em%Ikz8spWCuZ)vfYMx_!>h|kD?_3Y(rfoV=&p+r z=>EJ!G8xl!yFKm*fu1muJZVidy=E5YuZ7~oYDRCM37I(2R9e_sg5qtB)B0u+^$oir z?~DM^zSbdVk+Hxe8T6jSRwcDgTxN6E5NpM@=l3%IWwXm{*_EM4KiDvFg_xj=*_CAJ zFdi-QLA;@CjXgZ|q3o>wGiB4TltVJmNim1)xHcr?8Vh!YqD(+!Tw79Eu(MBES@g1a zD2YK`qCs7v!E8&325pH3ZHWe@M4@QQ1Vl=tGYG{<^dWD>uq}VGz(Fnc)>k_h5XU3#%RsSPyi z)gx*w?WO3)7Rptzm!`3+lt-MudwO_P$sSrs$Wvmxu zg)=05KKeCKx$r5Q6eT0QU)3h20<4-K7-weajf@e^sI9QF)wLxN4x7~F?v}O^jmyUxi_q75fnTzif=ezs9!zB)AzI1$7|j|0ltEuo|2VWZnN^@DcDq za5MO?;5}dm*apr5-^T9$d+=%SF7O**E7$_g1%H9fe+Re`90YSf_5k#Q(}4H`{t0{s z+y~^2fRBLR1zGShZ2McmTfiycer)*ngZF`BAP+WzUj|QOughHlw}T-d`yVnu?g@Ar zJO$nec7tK?67a9s>yLp?fe(TkKod-YOTn*!Uj?UtN3q@S0=I)d2EPxk1)D$)yb7ES zWr2vVEN=tm*Z zK`mS~+$0zBrVSU0GX^{*JmKtkc_Qs4@Ko^Y3CYMC%TqM}6O#<7wQ|vJXi;?*AGxY? zMN{zjd#_qAmVE0Gh=)~EW7vKXr;WD$cu2ItrZ7futgYI};eg7VLl{wUJFE@IefL*v z@we&YLfOO9{%m3su~{AF6j5w>c$Ndt%_+4mJmwxL`<3Zkg>B7Z&C6}uyKm(t_7hL9 z+#!c|SMII(awz~C1y)Y@eyPs7mp_#&yK)B*HwI1R7rd$2Qm$U86|4N9uAG$N7@=Ar zIIb1u@SsuKC1%7kg3YTcU_}2Ja1M^sW>x2TEGVZhC&K`JBe^Cfja0@0S*tPprcl`?Ybiykw4@ZIBPXSkp>p?*S+7<@ zMJt7F&fyy6^IHCnw8gw?jC%cBG10cwMky&meBbSHEObXQZ|w-WQMIgPN8&`;uq$jz zoUw(wkim)nR~IMk3ZWXKT1!Y}Nt>~&i$Ck7$w&hq z+SR&ByINPZx?05-qMXO8m{F-k(N)aa%d3J39^R6ut5r`RdOMe8_$N}9VO^GCU6x^6 z7P0^LU@)!_yAk`pYK+If#J0Z&$Q^)h16P4x2OGc-vFCpPz6JgO$lZUJfnNouf`7%H z|33IX;N##AK^4gU|CfUogD+sye*|0)Hi0Y{0MB8|%RPXy)?WsffaM?q{tdfcemCuZ zgX=&ZOoENzKY^#P?d5KOw}ETGVXzx)0+)i9gQu|b9|a!;H-brU3HTN8ee8TW2k=dB zFSrA|2fQ6z2WG%puoC%J!CS!9;8Gy= z|8v;*{{X%Y?g8%yZwB)FfaBnm;5qF5C%~J4+#@g!E(1Hj3&APipQgDZg4<+Gd- z_(9~HK&ww6x}Q>wHJqY1hvPAi=%Ia;W_il1^~nGoC6?#oijW{KRwQvVNXJsQ`&f)} z7(HC|IruNv+{JdgvL&Y|$HK*oGXw+96x;f%C3bmnO$P7X%Cy{s#g}@sDhJ&-;WeY~ z;%hjwZn^9_oYG=nj|{1{aN(}9_E%|^xQk%saG<|-@^$MKA|W+0A5$ueAv&qRYm$(iW~@pgba#=KO$@T`L-g+a9L% z`{fP3H}m$eeR%W|t|WJHmq%F82jb;)zuFvEYnJo=%nbWZ+1eG0YixW7)l-HAisfvf zEfn)Kle}ym%g2ptSTwl=o*{OB?!3Po<(aM$Iw><217;x)2i5+zta zGvPYzNJT+3oDHcgC&AcYrg=`)U&zQ_WpkbuUUI~XoT&}UCo!ySO8x+p17c$hWu>Z= zOnJ3(VZh;^c_J*VG|i&41#Z2eLaUN5_r}r`RD|4GQgqh901w=a&=OS{RFZPZPo`Ru zI}ET)tC_~YV0KL;s;YQ64`QenEv->AN1F^*@Pcx;h^+3?d@~K4L9RG~GE@2~%v2|4 z3TIMgN21&)=D7A@YUz1VX32tOWbruB1+^P0Dk7bDAXt;>lCmh2xV}ME0)0Z&L`3vK zPtz%?q6dlFMrHrDNJ`%Zvcf6IDz0b{{wWE(EY9Y{`!`1}aZu1TWWhSDTc;tk5G$ZUqPwpv?#|p@d&(iE!H{Y(k zwhgu7-tJV_U(#rosnR4vq)y}MN?o=scuPH|To<5MS`xidl})ZYPSI&ChlXSGb%`k- z)sb!2+Ec@SE!Sio$@F;@(Jj+`atp8Mgw(V9Dt4qRhi-U@6mutJV>fB03oI@&)iG2K zsoVPPS+Ru6EiDbU`djLFu&$L3oBy?t?ER=#fh@A&Urqsr`6S06U6Ia#1A&OKyrx(z z@Z3t@q}~5NjB)r4u@$lZJ!2?-3_Je=;1(e3|E~eE|9>Yq1$+uS{}bSy;2od@#=%%sT2>u&)!0kQXA1LVB_cd*|-0c5}aUN8t=4DP~a ze;*KA{*6G^`0HR0oCm&-&Hey*510l+Kz;=2Q+2AxFw)|JH-QNvv0u^BQ@JszX3Lfcb(_1S)+LWTt)wz^rwapwukz3KM z1)jVUQxXX}TeJZF=?OJ;c!~h9in5HguuzPzurOQ^N)wBeX7EW9iCO zz+&bXxRalwMO;hh;;ztVn8XfGEqLI=6+v*Jt`LT#5f&wOeCC08S`Sm|c>|7J<+lw} zlIY3aDDNUdjH@gzERM$(>LB%$q0cBy*2uUSH4&cBmBM*EaYYchHqmc3+Bs!}4@++R zLo-5)v76={N{}wu*`pg~($UjyX|!I&$TE8Q=XiI~ ze8_D2$Wr6znFrLix+YVTR&$OHRCXK*%Mxq6T6UZs4G*WGRP^z4 z*YfG~3nmh-T4H>%f;U9H&CeI7JwL1d->!D4w42EmeU){xBtTdCqa-gj*LiE#80zZ2IX%`1Hf5leiB5b5SbHV3dlb5ycAjP zXHLl>r3*``f;5R0Yuk(ex`%KnqU^+snh%G)N1~+gmgPqr)D8C=*w?PY&5H!nX;GV1 zxoutT4AGqEHXc`KlCOMH{S6W@W3EaM4&}yq783c;7$S#xQ&dU{hX~e~rq<$8EqTDv zor^QW@w{~FOg#szBBq=H)+r@Sw-#$i^?KXjkZe3#A=l}|+fP(kB&tKAXqI9vPRH0U zJ-Q{Auz;jrRj%{5XZ z!jIf?m+Uw4KAw7ns{y$J#HwB;E#wfBU7C#L2R%d+@#8YG^DNG-YRZx!Eo`=wU{K!6 z`T3R_mL%CJkE6nOX65JW%zfg~79%A|c9gcgArP4QSq%2il}c_^e)&pn15j=Y-8SSz z&@gvswD>p{YvEg{txf#u#Vo(Lg4GeRl_+FFzbD&zqy|I{>R%U=;@OWTNc2%l7?*cS e--wOP`YaJv0lf121<_Gizmquu-L9(Y